Skip to content

Commit 1361ac2

Browse files
committed
feat(admin)admin management built ,new role superadmin,double checking at login and admin layout
description: admin management has add admin with role admin or superadmin,list admin and remove admin with double checking super admin is the admin who can add new admins layout of admin has checking if they are admin or superadmin so changing url wont work anymore
1 parent da08cdb commit 1361ac2

16 files changed

Lines changed: 1163 additions & 65 deletions

File tree

app/admin/AdminMangement/page.tsx

Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
"use client";
2+
3+
import React, { useEffect, useState } from "react";
4+
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";
14+
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
15+
16+
export default function AdminPanel() {
17+
const [userRole, setUserRole] = useState<string | null>(null);
18+
const [loading, setLoading] = useState(true);
19+
20+
useEffect(() => {
21+
const fetchRole = async () => {
22+
const user = auth.currentUser;
23+
if (!user?.email) {
24+
setUserRole(null);
25+
setLoading(false);
26+
return;
27+
}
28+
29+
const docRef = doc(firestore, "adminemail", user.email);
30+
const docSnap = await getDoc(docRef);
31+
32+
if (docSnap.exists()) {
33+
setUserRole(docSnap.data().role);
34+
} else {
35+
setUserRole(null);
36+
}
37+
setLoading(false);
38+
};
39+
40+
fetchRole();
41+
}, []);
42+
43+
if (loading) return <div className="p-6">Loading...</div>;
44+
45+
if (userRole !== "superadmin") {
46+
return (
47+
<div className="p-6 text-red-600 font-semibold">
48+
You are not authorized to access this page.
49+
</div>
50+
);
51+
}
52+
53+
return (
54+
<div className="p-6">
55+
<h1 className="text-2xl font-bold mb-6">Admin Panel</h1>
56+
<Tabs defaultValue="add">
57+
<TabsList className="mb-4">
58+
<TabsTrigger value="add">Add Admin</TabsTrigger>
59+
<TabsTrigger value="list">List Admins</TabsTrigger>
60+
<TabsTrigger value="remove">Remove Admin</TabsTrigger>
61+
</TabsList>
62+
<TabsContent value="add">
63+
<AddAdmin />
64+
</TabsContent>
65+
<TabsContent value="list">
66+
<AdminList />
67+
</TabsContent>
68+
<TabsContent value="remove">
69+
<RemoveAdmin />
70+
</TabsContent>
71+
</Tabs>
72+
</div>
73+
);
74+
}
75+
76+
const AddAdmin = () => {
77+
const [email, setEmail] = useState("");
78+
const [role, setRole] = useState<"admin" | "superadmin">("admin");
79+
const [status, setStatus] = useState<string | null>(null);
80+
const [loading, setLoading] = useState(false);
81+
const [confirming, setConfirming] = useState(false);
82+
83+
const handleAdd = async () => {
84+
setStatus(null);
85+
setLoading(true);
86+
const currentUser = auth.currentUser;
87+
if (!currentUser) {
88+
setStatus("Must be logged in.");
89+
setLoading(false);
90+
return;
91+
}
92+
93+
const currentUserRef = doc(firestore, "adminemail", currentUser.email!);
94+
const currentUserSnap = await getDoc(currentUserRef);
95+
96+
if (!currentUserSnap.exists() || currentUserSnap.data().role !== "superadmin") {
97+
setStatus("Not authorized.");
98+
setLoading(false);
99+
return;
100+
}
101+
102+
try {
103+
const targetRef = doc(firestore, "adminemail", email);
104+
const targetSnap = await getDoc(targetRef);
105+
106+
// If already exists, delete first
107+
if (targetSnap.exists()) {
108+
await deleteDoc(targetRef);
109+
}
110+
111+
await setDoc(targetRef, {
112+
role,
113+
addedBy: currentUser.email,
114+
addedAt: Timestamp.now(),
115+
isActive: true,
116+
});
117+
118+
setStatus(`Admin added successfully with role: ${role}`);
119+
setEmail("");
120+
setRole("admin");
121+
setConfirming(false);
122+
} catch (err: unknown) {
123+
if (err instanceof Error) {
124+
setStatus("Error: " + err.message);
125+
} else {
126+
setStatus("An unknown error occurred.");
127+
}
128+
} finally {
129+
setLoading(false);
130+
}
131+
};
132+
133+
return (
134+
<div>
135+
<input
136+
type="email"
137+
placeholder="Admin email"
138+
value={email}
139+
onChange={(e) => setEmail(e.target.value)}
140+
className="border p-2 w-full rounded mb-2"
141+
/>
142+
<select
143+
value={role}
144+
onChange={(e) => setRole(e.target.value as "admin" | "superadmin")}
145+
className="border p-2 w-full rounded mb-2"
146+
>
147+
<option value="admin">Admin</option>
148+
<option value="superadmin">Superadmin</option>
149+
</select>
150+
151+
{!confirming ? (
152+
<button
153+
onClick={() => setConfirming(true)}
154+
disabled={loading || !email}
155+
className="bg-blue-600 text-white px-4 py-2 rounded"
156+
>
157+
Add Admin
158+
</button>
159+
) : (
160+
<div className="space-x-4">
161+
<button
162+
onClick={handleAdd}
163+
disabled={loading}
164+
className="bg-green-600 text-white px-4 py-2 rounded"
165+
>
166+
Confirm Add
167+
</button>
168+
<button
169+
onClick={() => setConfirming(false)}
170+
disabled={loading}
171+
className="bg-gray-300 px-4 py-2 rounded"
172+
>
173+
Cancel
174+
</button>
175+
</div>
176+
)}
177+
178+
{status && <p className="mt-2 text-sm">{status}</p>}
179+
</div>
180+
);
181+
};
182+
183+
const AdminList = () => {
184+
interface Admin {
185+
id: string;
186+
role: string;
187+
addedBy: string;
188+
addedAt: Timestamp;
189+
isActive: boolean;
190+
}
191+
192+
const [admins, setAdmins] = useState<Admin[]>([]);
193+
194+
useEffect(() => {
195+
const fetchAdmins = async () => {
196+
const snap = await getDocs(collection(firestore, "adminemail"));
197+
const list = snap.docs.map((doc) => ({
198+
id: doc.id,
199+
role: doc.data().role,
200+
addedBy: doc.data().addedBy,
201+
addedAt: doc.data().addedAt,
202+
isActive: doc.data().isActive,
203+
}));
204+
setAdmins(list);
205+
};
206+
fetchAdmins();
207+
}, []);
208+
209+
return (
210+
<div>
211+
{admins.map((admin) => (
212+
<div
213+
key={admin.id}
214+
className="border rounded p-4 mb-2 shadow-sm bg-gray-50"
215+
>
216+
<p>
217+
<strong>Email:</strong> {admin.id}
218+
</p>
219+
<p>
220+
<strong>Role:</strong> {admin.role}
221+
</p>
222+
<p>
223+
<strong>Added By:</strong> {admin.addedBy}
224+
</p>
225+
<p>
226+
<strong>Added At:</strong>{" "}
227+
{admin.addedAt?.toDate().toLocaleString()}
228+
</p>
229+
</div>
230+
))}
231+
</div>
232+
);
233+
};
234+
235+
const RemoveAdmin = () => {
236+
const [email, setEmail] = useState("");
237+
const [status, setStatus] = useState<string | null>(null);
238+
const [confirming, setConfirming] = useState(false);
239+
240+
const handleRemove = async () => {
241+
setStatus(null);
242+
const currentUser = auth.currentUser;
243+
if (!currentUser) {
244+
setStatus("Must be logged in.");
245+
return;
246+
}
247+
248+
const ref = doc(firestore, "adminemail", currentUser.email!);
249+
const snap = await getDoc(ref);
250+
if (!snap.exists() || snap.data().role !== "superadmin") {
251+
setStatus("Not authorized.");
252+
return;
253+
}
254+
255+
try {
256+
const removedAdmin = await getDoc(doc(firestore, "adminemail", email));
257+
if (!removedAdmin.exists()) {
258+
setStatus("Admin not found.");
259+
return;
260+
}
261+
262+
await deleteDoc(doc(firestore, "adminemail", email));
263+
264+
await setDoc(doc(firestore, "removedadmin", email), {
265+
email,
266+
removedBy: currentUser.email,
267+
removedAt: Timestamp.now(),
268+
roleAtRemoval: removedAdmin.data().role,
269+
});
270+
271+
setStatus("Admin removed.");
272+
setEmail("");
273+
setConfirming(false);
274+
} catch (err: unknown) {
275+
if (err instanceof Error) {
276+
setStatus("Error: " + err.message);
277+
} else {
278+
setStatus("An unknown error occurred.");
279+
}
280+
}
281+
};
282+
283+
return (
284+
<div>
285+
<input
286+
type="email"
287+
placeholder="Admin email to remove"
288+
value={email}
289+
onChange={(e) => setEmail(e.target.value)}
290+
className="border p-2 w-full rounded mb-2"
291+
/>
292+
{!confirming ? (
293+
<button
294+
onClick={() => setConfirming(true)}
295+
className="bg-red-600 text-white px-4 py-2 rounded"
296+
>
297+
Remove Admin
298+
</button>
299+
) : (
300+
<div className="space-x-4">
301+
<button
302+
onClick={handleRemove}
303+
className="bg-red-600 text-white px-4 py-2 rounded"
304+
>
305+
Yes, Remove
306+
</button>
307+
<button
308+
onClick={() => setConfirming(false)}
309+
className="bg-gray-300 px-4 py-2 rounded"
310+
>
311+
Cancel
312+
</button>
313+
</div>
314+
)}
315+
{status && <p className="mt-2 text-sm">{status}</p>}
316+
<RemovedAdminsList />
317+
</div>
318+
);
319+
};
320+
321+
const RemovedAdminsList = () => {
322+
interface RemovedAdmin {
323+
id: string;
324+
email: string;
325+
removedBy: string;
326+
roleAtRemoval: string;
327+
removedAt: Timestamp;
328+
}
329+
330+
const [removed, setRemoved] = useState<RemovedAdmin[]>([]);
331+
332+
useEffect(() => {
333+
const fetchRemoved = async () => {
334+
const snap = await getDocs(collection(firestore, "removedadmin"));
335+
const list = snap.docs.map((doc) => ({
336+
id: doc.id,
337+
email: doc.data().email,
338+
removedBy: doc.data().removedBy,
339+
roleAtRemoval: doc.data().roleAtRemoval,
340+
removedAt: doc.data().removedAt,
341+
}));
342+
setRemoved(list);
343+
};
344+
fetchRemoved();
345+
}, []);
346+
347+
return (
348+
<div className="mt-6">
349+
<h3 className="font-semibold mb-2">Removed Admins</h3>
350+
{removed.map((admin) => (
351+
<div
352+
key={admin.id}
353+
className="border rounded p-4 mb-2 shadow-sm bg-red-50"
354+
>
355+
<p>
356+
<strong>Email:</strong> {admin.email}
357+
</p>
358+
<p>
359+
<strong>Removed By:</strong> {admin.removedBy}
360+
</p>
361+
<p>
362+
<strong>Role:</strong> {admin.roleAtRemoval}
363+
</p>
364+
<p>
365+
<strong>Removed At:</strong>{" "}
366+
{admin.removedAt?.toDate().toLocaleString()}
367+
</p>
368+
</div>
369+
))}
370+
</div>
371+
);
372+
};

app/admin/ManageAdmin/page.tsx

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)