Skip to content

Commit 64709bd

Browse files
committed
Profile page created with edit and delete functions,ui of some pages enhanced
1 parent 4e7d931 commit 64709bd

15 files changed

Lines changed: 2889 additions & 1152 deletions

File tree

app/add-project/page.tsx

Lines changed: 67 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import React, { useState, useEffect } from "react";
44
import { ArrowLeft, Save, XCircle } from "lucide-react";
55
import Link from "next/link";
6+
import { auth } from "@/lib/firebase/config"; // Import auth
67

7-
interface CategoryOption { // Define interfaces for fetched data
8+
interface CategoryOption {
89
optionId: number;
910
optionName: string;
1011
}
@@ -23,19 +24,18 @@ const AddProjectPage = () => {
2324
useEffect(() => {
2425
const fetchCategories = async () => {
2526
try {
26-
const res = await fetch('/api/categories');
27-
if (!res.ok) {
28-
throw new Error(`HTTP error! status: ${res.status}`);
29-
}
27+
const res = await fetch("/api/categories");
28+
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`);
3029
const data: Category[] = await res.json();
3130
setCategories(data);
3231
} catch (e: any) {
3332
setErrorCategories(e.message);
34-
console.error("Failed to fetch categories for form:", e);
33+
console.error("Failed to fetch categories:", e);
3534
} finally {
3635
setLoadingCategories(false);
3736
}
3837
};
38+
3939
fetchCategories();
4040
}, []);
4141

@@ -45,35 +45,31 @@ const AddProjectPage = () => {
4545
projectLink: "",
4646
createdAt: "",
4747
members: [{ name: "", linkedin: "" }],
48-
selectedCategoryOptions: {} as Record<string, string>, // Map category name to selected option name
49-
customDomain: "", // Keep customDomain separate if 'Domain' is 'Other'
48+
selectedCategoryOptions: {} as Record<string, string>,
49+
customDomain: "",
5050
};
5151

5252
const [formData, setFormData] = useState(initialFormState);
5353
const [showPopup, setShowPopup] = useState(false);
5454
const [loading, setLoading] = useState(false);
5555

5656
useEffect(() => {
57-
// Set initial default values for dropdowns after categories are fetched
5857
if (!loadingCategories && categories.length > 0) {
59-
setFormData(prev => {
58+
setFormData((prev) => {
6059
const newSelectedOptions: Record<string, string> = {};
61-
categories.forEach(cat => {
60+
categories.forEach((cat) => {
6261
if (cat.options.length > 0) {
6362
newSelectedOptions[cat.categoryName] = cat.options[0].optionName;
6463
}
6564
});
66-
return {
67-
...prev,
68-
selectedCategoryOptions: newSelectedOptions,
69-
};
65+
return { ...prev, selectedCategoryOptions: newSelectedOptions };
7066
});
7167
}
7268
}, [loadingCategories, categories]);
7369

7470
const handleSubmit = async (e: React.FormEvent) => {
7571
e.preventDefault();
76-
if (loading) return;
72+
if (loading) return;
7773

7874
const hasValidMember = formData.members.some(
7975
(member) => member.name.trim() !== ""
@@ -89,10 +85,14 @@ const AddProjectPage = () => {
8985
(member) => member.name.trim() !== ""
9086
);
9187

92-
// Prepare category options for backend
93-
const projectCategoryOptions: { categoryName: string; optionName: string }[] = Object.entries(formData.selectedCategoryOptions).map(([categoryName, optionName]) => ({
88+
const projectCategoryOptions :{categoryName: string; optionName:string}[]= Object.entries(
89+
formData.selectedCategoryOptions
90+
).map(([categoryName, optionName]) => ({
9491
categoryName,
95-
optionName: categoryName === 'Domain' && optionName === 'Other' ? formData.customDomain : optionName // Use customDomain if 'Other' domain is selected
92+
optionName:
93+
categoryName === "Domain" && optionName === "Other"
94+
? formData.customDomain
95+
: optionName,
9696
}));
9797

9898
const projectData = {
@@ -101,20 +101,35 @@ const AddProjectPage = () => {
101101
projectLink: formData.projectLink,
102102
createdAt: new Date().toISOString(),
103103
members: filteredMembers,
104-
projectCategoryOptions, // Send as a generic array of category options
105-
customDomain: formData.selectedCategoryOptions['Domain'] === 'Other' ? formData.customDomain : undefined, // Send customDomain separately
104+
projectCategoryOptions,
105+
customDomain:
106+
formData.selectedCategoryOptions["Domain"] === "Other"
107+
? formData.customDomain
108+
: undefined,
106109
};
107110

108111
try {
112+
const user = auth.currentUser;
113+
if (!user) {
114+
alert("You must be logged in to submit a project.");
115+
setLoading(false);
116+
return;
117+
}
118+
119+
const idToken = await user.getIdToken();
120+
document.cookie = `__session=${idToken}; path=/;`;
121+
109122
const response = await fetch("/api/projects", {
110123
method: "POST",
111-
headers: { "Content-Type": "application/json" },
112-
body: JSON.stringify(projectData)
124+
headers: {
125+
"Content-Type": "application/json",
126+
},
127+
body: JSON.stringify(projectData),
113128
});
114129

115130
if (response.ok) {
116-
localStorage.removeItem('cachedProjects'); // Invalidate projects cache
117-
setFormData(initialFormState); // Reset form
131+
localStorage.removeItem("cachedProjects");
132+
setFormData(initialFormState);
118133
setShowPopup(true);
119134
setTimeout(() => setShowPopup(false), 3000);
120135
} else {
@@ -156,7 +171,7 @@ const AddProjectPage = () => {
156171
const addMember = () => {
157172
setFormData({
158173
...formData,
159-
members: [...formData.members, { name: "", linkedin: "" }]
174+
members: [...formData.members, { name: "", linkedin: "" }],
160175
});
161176
};
162177

@@ -174,7 +189,11 @@ const AddProjectPage = () => {
174189
}
175190

176191
if (errorCategories) {
177-
return <div className="text-center py-8 text-red-500">Error loading form data: {errorCategories}</div>;
192+
return (
193+
<div className="text-center py-8 text-red-500">
194+
Error loading form data: {errorCategories}
195+
</div>
196+
);
178197
}
179198

180199
return (
@@ -188,24 +207,9 @@ const AddProjectPage = () => {
188207
</div>
189208
</div>
190209

191-
{/* Pop-up Message */}
192210
{showPopup && (
193-
<div
194-
style={{
195-
position: "fixed",
196-
top: "20%",
197-
left: "50%",
198-
transform: "translateX(-50%)",
199-
backgroundColor: "#4CAF50",
200-
color: "white",
201-
padding: "20px",
202-
borderRadius: "10px",
203-
fontSize: "20px",
204-
fontWeight: "bold",
205-
animation: "popIn 0.6s ease-in-out"
206-
}}
207-
>
208-
🎉 Congratulations! Your project was saved successfully! 🎉
211+
<div className="fixed top-20 left-1/2 transform -translate-x-1/2 bg-green-500 text-white px-6 py-4 rounded-lg shadow-lg z-50">
212+
🎉 Congratulations! Your project was saved successfully!
209213
</div>
210214
)}
211215

@@ -232,37 +236,39 @@ const AddProjectPage = () => {
232236
onChange={handleChange}
233237
value={formData.projectDescription}
234238
/>
235-
236-
{/* Dynamic Category Selects */}
237-
{categories.length > 0 && categories.map(category => (
239+
240+
{categories.length>0 && categories.map((category) => (
238241
<div key={category.categoryId} className="mb-4">
239-
<label htmlFor={`category-${category.categoryName}`} className="block text-sm font-medium text-gray-700">
242+
<label className="block mb-1 text-sm font-medium">
240243
{category.categoryName}
241244
</label>
242245
<select
243-
id={`category-${category.categoryName}`}
246+
id={`category-${category.categoryName}`}
244247
name={`category-${category.categoryName}`}
245248
required
246249
className="w-full px-4 py-2 border rounded-lg"
247250
onChange={handleChange}
248-
value={formData.selectedCategoryOptions[category.categoryName] || ''}
251+
value={
252+
formData.selectedCategoryOptions[category.categoryName] || ""
253+
}
249254
>
250255
{category.options.map((option) => (
251256
<option key={option.optionId} value={option.optionName}>
252257
{option.optionName}
253258
</option>
254259
))}
255260
</select>
256-
{category.categoryName === "Domain" && formData.selectedCategoryOptions["Domain"] === "Other" && (
257-
<input
258-
type="text"
259-
name="customDomain"
260-
className="mt-2 w-full px-4 py-2 border rounded-lg"
261-
placeholder="Enter custom domain"
262-
onChange={handleChange}
263-
value={formData.customDomain}
264-
/>
265-
)}
261+
{category.categoryName === "Domain" &&
262+
formData.selectedCategoryOptions["Domain"] === "Other" && (
263+
<input
264+
type="text"
265+
name="customDomain"
266+
className="mt-2 w-full px-4 py-2 border rounded-lg"
267+
placeholder="Enter custom domain"
268+
onChange={handleChange}
269+
value={formData.customDomain}
270+
/>
271+
)}
266272
</div>
267273
))}
268274

@@ -271,12 +277,11 @@ const AddProjectPage = () => {
271277
name="projectLink"
272278
required
273279
className="w-full px-4 py-2 border rounded-lg"
274-
placeholder="Github link or google drive link of project contents"
280+
placeholder="Project Link (e.g. GitHub)"
275281
onChange={handleChange}
276282
value={formData.projectLink}
277283
/>
278284

279-
{/* Members Section */}
280285
{formData.members.map((member, index) => (
281286
<div key={index} className="flex items-center gap-2">
282287
<input

app/admin/super-admin/page.tsx

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
11
"use client";
22

33
import React, { useEffect, useState } from "react";
4+
import { useRouter } from "next/navigation";
5+
import { ArrowLeft } from "lucide-react";
46
import { firestore, auth } from "@/lib/firebase/config";
5-
import {
6-
collection,
7-
doc,
8-
getDoc,
9-
setDoc,
10-
deleteDoc,
11-
getDocs,
12-
Timestamp,
13-
} from "firebase/firestore";
7+
import { doc, getDoc } from "firebase/firestore";
148
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
15-
import { AddAdmin, AdminList, RemoveAdmin, RemovedAdminsList } from "@/components/admin-components";
9+
import {
10+
AddAdmin,
11+
AdminList,
12+
RemoveAdmin,
13+
} from "@/components/admin-components";
1614

1715
export default function AdminPanel() {
1816
const [userRole, setUserRole] = useState<string | null>(null);
1917
const [loading, setLoading] = useState(true);
18+
const router = useRouter();
2019

2120
useEffect(() => {
2221
const fetchRole = async () => {
@@ -41,35 +40,76 @@ export default function AdminPanel() {
4140
fetchRole();
4241
}, []);
4342

44-
if (loading) return <div className="p-6">Loading...</div>;
43+
if (loading)
44+
return (
45+
<div className="min-h-screen flex items-center justify-center bg-white">
46+
<div className="text-center">
47+
<div className="w-12 h-12 border-4 border-blue-300 border-t-blue-600 rounded-full animate-spin mx-auto"></div>
48+
<p className="mt-4 text-blue-700 font-medium animate-pulse">
49+
Loading...
50+
</p>
51+
</div>
52+
</div>
53+
);
4554

4655
if (userRole !== "superadmin") {
4756
return (
48-
<div className="p-6 text-red-600 font-semibold">
49-
You are not authorized to access this page.
57+
<div className="min-h-screen flex items-center justify-center bg-white">
58+
<div className="bg-red-100 border border-red-300 text-red-700 px-6 py-4 rounded shadow-sm text-center">
59+
<p className="font-semibold">
60+
You are not authorized to access this page.
61+
</p>
62+
</div>
5063
</div>
5164
);
5265
}
5366

5467
return (
55-
<div className="p-6">
56-
<h1 className="text-2xl font-bold mb-6">Admin Panel</h1>
57-
<Tabs defaultValue="add">
58-
<TabsList className="mb-4">
59-
<TabsTrigger value="add">Add Admin</TabsTrigger>
60-
<TabsTrigger value="list">List Admins</TabsTrigger>
61-
<TabsTrigger value="remove">Remove Admin</TabsTrigger>
62-
</TabsList>
63-
<TabsContent value="add">
64-
<AddAdmin />
65-
</TabsContent>
66-
<TabsContent value="list">
67-
<AdminList />
68-
</TabsContent>
69-
<TabsContent value="remove">
70-
<RemoveAdmin />
71-
</TabsContent>
72-
</Tabs>
68+
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-white p-8">
69+
<div className="max-w-3xl mx-auto bg-white shadow-xl rounded-2xl p-6 transition-all duration-300">
70+
{/* Back Button */}
71+
<button
72+
onClick={() => router.push("/profile")}
73+
className="flex items-center text-blue-600 hover:text-blue-800 transition-colors duration-200 mb-4"
74+
>
75+
<ArrowLeft className="w-5 h-5 mr-2" />
76+
<span className="font-medium">Back to Profile</span>
77+
</button>
78+
<h1 className="text-3xl font-extrabold text-blue-700 mb-6 text-center">
79+
Super Admin Panel
80+
</h1>
81+
<Tabs defaultValue="list" className="w-full">
82+
<TabsList className="flex justify-center space-x-2 bg-blue-100 p-1 rounded-lg mb-6">
83+
<TabsTrigger
84+
value="list"
85+
className="data-[state=active]:bg-blue-600 data-[state=active]:text-white text-blue-700 px-4 py-2 rounded-lg font-medium transition-all duration-200 hover:bg-blue-200"
86+
>
87+
Admin List
88+
</TabsTrigger>
89+
<TabsTrigger
90+
value="add"
91+
className="data-[state=active]:bg-blue-600 data-[state=active]:text-white text-blue-700 px-4 py-2 rounded-lg font-medium transition-all duration-200 hover:bg-blue-200"
92+
>
93+
Add Admin
94+
</TabsTrigger>
95+
<TabsTrigger
96+
value="remove"
97+
className="data-[state=active]:bg-blue-600 data-[state=active]:text-white text-blue-700 px-4 py-2 rounded-lg font-medium transition-all duration-200 hover:bg-blue-200"
98+
>
99+
Remove Admin
100+
</TabsTrigger>
101+
</TabsList>
102+
<TabsContent value="list" className="animate-fadeIn">
103+
<AdminList />
104+
</TabsContent>
105+
<TabsContent value="add" className="animate-fadeIn">
106+
<AddAdmin />
107+
</TabsContent>
108+
<TabsContent value="remove" className="animate-fadeIn">
109+
<RemoveAdmin />
110+
</TabsContent>
111+
</Tabs>
112+
</div>
73113
</div>
74114
);
75-
}
115+
}

0 commit comments

Comments
 (0)