diff --git a/src/app/client-layout.js b/src/app/client-layout.js index 5f1dafa..6258504 100644 --- a/src/app/client-layout.js +++ b/src/app/client-layout.js @@ -1,7 +1,7 @@ 'use client'; import { useState, useEffect } from "react"; import { account } from "./lib/appwrite"; -import { useRouter } from "next/navigation"; +import { useRouter, usePathname } from "next/navigation"; import Sidebar from "./components/Sidebar"; import Navbar from "./components/Navbar"; import { Menu } from 'lucide-react'; @@ -12,21 +12,34 @@ export default function ClientLayout({ children }) { const [isLoggedIn, setIsLoggedIn] = useState(false); const [loading, setLoading] = useState(true); const router = useRouter(); + const pathname = usePathname(); + + // List of routes where we don't want sidebar/navbar + const authRoutes = ['/login', '/register', '/forgot-password']; useEffect(() => { const checkAuth = async () => { try { await account.get(); setIsLoggedIn(true); + + // If on auth route but logged in, redirect to dashboard + if (authRoutes.includes(pathname)) { + router.push('/dashboard'); + } } catch (error) { setIsLoggedIn(false); - router.push('/login'); + + // If not on auth route and not logged in, redirect to login + if (!authRoutes.includes(pathname)) { + router.push('/login'); + } } finally { setLoading(false); } }; checkAuth(); - }, [router]); + }, [router, pathname]); const toggleMobileSidebar = () => { setIsMobileOpen(!isMobileOpen); @@ -36,19 +49,24 @@ export default function ClientLayout({ children }) { return
Loading...
; } - if (!isLoggedIn) { + // If on auth route, render only the children without layout + if (authRoutes.includes(pathname)) { return ( -
-
- {children} -
+
+ {children}
); } + // If not logged in but not on auth route (shouldn't happen due to redirect above) + if (!isLoggedIn) { + return null; // or redirect to login + } + + // Normal layout with sidebar and navbar return (
- {/* Mobile Toggle Button (outside sidebar) */} + {/* Mobile Toggle Button */} + +
+
+ + + ); +} \ No newline at end of file diff --git a/src/app/components/MemberForm.js b/src/app/components/MemberForm.js index 01fa748..e1f8d44 100644 --- a/src/app/components/MemberForm.js +++ b/src/app/components/MemberForm.js @@ -1,14 +1,56 @@ -import { useState } from "react"; -import { databases, ID, databaseId, membersCollectionId } from "../lib/appwrite"; +'use client'; + +import { useState, useEffect } from "react"; +import { databases, ID, databaseId, membersCollectionId, Query } from "../lib/appwrite"; +import { getCurrentUserWithRole } from "../utils/auth"; export default function MemberForm() { + const [currentUser, setCurrentUser] = useState(null); + const [loading, setLoading] = useState(true); const [form, setForm] = useState({ name: "", email: "", phone: "", - role: "", - joinDate: "", + role: "member", + joinDate: new Date().toISOString().split('T')[0], }); + const [error, setError] = useState(""); + + useEffect(() => { + const fetchCurrentUser = async () => { + try { + const user = await getCurrentUserWithRole(); + setCurrentUser(user); + + setForm(prev => ({ + ...prev, + name: user.name || "", + email: user.email || "" + })); + } catch (err) { + console.error("Failed to fetch current user:", err); + setError("Failed to load user data"); + } finally { + setLoading(false); + } + }; + + fetchCurrentUser(); + }, []); + + const checkDuplicateEmail = async (email) => { + try { + const response = await databases.listDocuments( + databaseId, + membersCollectionId, + [Query.equal("email", email)] + ); + return response.documents.length > 0; + } catch (err) { + console.error("Error checking duplicate email:", err); + return false; + } + }; const handleChange = (e) => { setForm({ ...form, [e.target.name]: e.target.value }); @@ -16,6 +58,21 @@ export default function MemberForm() { const handleSubmit = async (e) => { e.preventDefault(); + setError(""); + + // Validate email matches current user + if (form.email !== currentUser?.email) { + setError("You must use your registered email address"); + return; + } + + // Check for duplicate entry + const isDuplicate = await checkDuplicateEmail(form.email); + if (isDuplicate) { + setError("This email is already registered as a member"); + return; + } + try { await databases.createDocument( databaseId, @@ -23,20 +80,119 @@ export default function MemberForm() { ID.unique(), form ); - alert("Member added!"); + alert("Member added successfully!"); + setForm(prev => ({ + ...prev, + phone: "", + role: "member", + joinDate: new Date().toISOString().split('T')[0] + })); } catch (err) { console.error("Failed to add member:", err); + setError("Failed to add member. Please try again."); } }; + if (loading) { + return
Loading form...
; + } + return ( -
- - - - - - -
+
+

Add New Member

+ + {error && ( +
+ {error} +
+ )} + +
+ {/* First Row - Name and Email */} +
+ {/* Name Field */} +
+ +
+ +
+ (your account name) +
+
+
+ + {/* Email Field */} +
+ +
+ +
+ (your login email) +
+
+
+
+ + {/* Second Row - Phone and Role */} +
+ {/* Phone Field */} +
+ + +
+ + {/* Role Field */} +
+ + +
+
+ + {/* Join Date Field */} +
+ + +
+ + +
+ +
); -} +} \ No newline at end of file diff --git a/src/app/components/MemberList.js b/src/app/components/MemberList.js index c666099..c7a58c5 100644 --- a/src/app/components/MemberList.js +++ b/src/app/components/MemberList.js @@ -6,24 +6,87 @@ import { databaseId, membersCollectionId } from "../lib/appwrite"; import { getCurrentUserWithRole } from "../utils/auth"; import { Query } from "appwrite"; +// Import icons +const EditIcon = () => ( + + + +); + +const DeleteIcon = () => ( + + + +); + +const SaveIcon = () => ( + + + +); + +const CancelIcon = () => ( + + + +); + +// Confirmation Modal Component +const ConfirmationModal = ({ isOpen, onClose, onConfirm, title, message }) => { + if (!isOpen) return null; + + return ( +
+
+
+

{title}

+

{message}

+ +
+ + +
+
+
+
+ ); +}; + export default function MemberList() { const [members, setMembers] = useState([]); const [currentUser, setCurrentUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); + const [editingId, setEditingId] = useState(null); + const [editFormData, setEditFormData] = useState({ + name: '', + email: '', + phone: '', + role: '' + }); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [memberToDelete, setMemberToDelete] = useState(null); useEffect(() => { const fetchData = async () => { try { - // Get current user with role first const user = await getCurrentUserWithRole(); setCurrentUser(user); - // Then fetch members with proper error handling const res = await databases.listDocuments( databaseId, membersCollectionId, - [Query.orderDesc("$createdAt")] // Optional: sort by creation date + [Query.orderDesc("$createdAt")] ); setMembers(res.documents); @@ -38,6 +101,76 @@ export default function MemberList() { fetchData(); }, []); + const handleEditClick = (member) => { + setEditingId(member.$id); + setEditFormData({ + name: member.name, + email: member.email, + phone: member.phone, + role: member.role + }); + }; + + const handleEditFormChange = (e) => { + const { name, value } = e.target; + setEditFormData({ + ...editFormData, + [name]: value + }); + }; + + const handleUpdate = async (memberId) => { + try { + await databases.updateDocument( + databaseId, + membersCollectionId, + memberId, + editFormData + ); + + const res = await databases.listDocuments( + databaseId, + membersCollectionId, + [Query.orderDesc("$createdAt")] + ); + setMembers(res.documents); + setEditingId(null); + } catch (err) { + console.error("Error updating member:", err); + setError("Failed to update member"); + } + }; + + const handleDeleteClick = (memberId) => { + setMemberToDelete(memberId); + setShowDeleteModal(true); + }; + + const handleDeleteConfirm = async () => { + if (!memberToDelete) return; + + try { + await databases.deleteDocument( + databaseId, + membersCollectionId, + memberToDelete + ); + + const res = await databases.listDocuments( + databaseId, + membersCollectionId, + [Query.orderDesc("$createdAt")] + ); + setMembers(res.documents); + } catch (err) { + console.error("Error deleting member:", err); + setError("Failed to delete member"); + } finally { + setShowDeleteModal(false); + setMemberToDelete(null); + } + }; + if (loading) { return
Loading members...
; } @@ -50,7 +183,6 @@ export default function MemberList() {

All Members

- {/* Current user info */} {currentUser && (

You are logged in as: {currentUser.name}

@@ -58,7 +190,6 @@ export default function MemberList() {
)} - {/* Members table */}
@@ -68,32 +199,115 @@ export default function MemberList() { + {currentUser?.role === 'admin' && } {members.length > 0 ? ( members.map((member) => ( - - - - - + {editingId === member.$id ? ( + <> + + + + + + + + ) : ( + <> + + + + + + {currentUser?.role === 'admin' && ( + + )} + + )} )) ) : ( - @@ -101,6 +315,14 @@ export default function MemberList() {
Phone Join Date RoleActions
{member.name || "N/A"}{member.email || "N/A"}{member.phone || "N/A"} - {member.joinDate ? new Date(member.joinDate).toLocaleDateString() : "N/A"} - - - {member.role || "unknown"} - - + + + + + + + {member.joinDate ? new Date(member.joinDate).toLocaleDateString() : "N/A"} + + + + + + {member.name || "N/A"}{member.email || "N/A"}{member.phone || "N/A"} + {member.joinDate ? new Date(member.joinDate).toLocaleDateString() : "N/A"} + + + {member.role || "unknown"} + + + + +
+ No members found
+ + setShowDeleteModal(false)} + onConfirm={handleDeleteConfirm} + title="Delete Member" + message="Are you sure you want to delete this member? This action cannot be undone." + />
); } \ No newline at end of file diff --git a/src/app/components/Navbar.jsx b/src/app/components/Navbar.jsx index f21c804..08151ea 100644 --- a/src/app/components/Navbar.jsx +++ b/src/app/components/Navbar.jsx @@ -62,7 +62,7 @@ export default function Navbar({ toggleSidebar }) { }; return ( -