Removed sidebar ,navbar from authentication page. Added confirmation component.
This commit is contained in:
parent
26b8410da0
commit
8f931a7403
@ -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 <div className="flex items-center justify-center min-h-screen">Loading...</div>;
|
||||
}
|
||||
|
||||
if (!isLoggedIn) {
|
||||
// If on auth route, render only the children without layout
|
||||
if (authRoutes.includes(pathname)) {
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
<main className="p-4 md:p-6">
|
||||
{children}
|
||||
</main>
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 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 (
|
||||
<div className="flex min-h-screen">
|
||||
{/* Mobile Toggle Button (outside sidebar) */}
|
||||
{/* Mobile Toggle Button */}
|
||||
<button
|
||||
onClick={toggleMobileSidebar}
|
||||
className="md:hidden fixed top-4 left-4 z-50 bg-white dark:bg-gray-800 p-2 rounded-md shadow-md"
|
||||
|
41
src/app/components/ConfirmationModal.js
Normal file
41
src/app/components/ConfirmationModal.js
Normal file
@ -0,0 +1,41 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
export default function ConfirmationModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
onConfirm,
|
||||
title = "Confirm Action",
|
||||
message = "Are you sure you want to perform this action?",
|
||||
confirmText = "Confirm",
|
||||
cancelText = "Cancel"
|
||||
}) {
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white rounded-lg shadow-xl max-w-md w-full">
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">{title}</h3>
|
||||
<p className="text-gray-600 mb-6">{message}</p>
|
||||
|
||||
<div className="flex justify-end space-x-3">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
{cancelText}
|
||||
</button>
|
||||
<button
|
||||
onClick={onConfirm}
|
||||
className="px-4 py-2 bg-red-600 text-white rounded-md text-sm font-medium hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||
>
|
||||
{confirmText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -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 <div className="text-center p-4">Loading form...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<input name="name" onChange={handleChange} placeholder="Name" />
|
||||
<input name="email" onChange={handleChange} placeholder="Email" />
|
||||
<input name="phone" onChange={handleChange} placeholder="Phone" />
|
||||
<input name="role" onChange={handleChange} placeholder="Role" />
|
||||
<input type="date" name="joinDate" onChange={handleChange} />
|
||||
<button type="submit">Add Member</button>
|
||||
</form>
|
||||
<div className="max-w-2xl mx-auto p-6 bg-white rounded-lg shadow-md">
|
||||
<h2 className="text-xl font-semibold mb-4">Add New Member</h2>
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 p-3 bg-red-100 text-red-700 rounded-md">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* First Row - Name and Email */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* Name Field */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Name</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
name="name"
|
||||
value={form.name}
|
||||
className="w-full px-3 py-1.5 border rounded-md bg-gray-100 text-sm"
|
||||
readOnly
|
||||
/>
|
||||
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
|
||||
<span className="text-gray-500 text-xs">(your account name)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Email Field */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Email</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={form.email}
|
||||
className="w-full px-3 py-1.5 border rounded-md bg-gray-100 text-sm"
|
||||
readOnly
|
||||
/>
|
||||
<div className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
|
||||
<span className="text-gray-500 text-xs">(your login email)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Second Row - Phone and Role */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* Phone Field */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Phone</label>
|
||||
<input
|
||||
name="phone"
|
||||
value={form.phone}
|
||||
onChange={handleChange}
|
||||
placeholder="Enter phone number"
|
||||
className="w-full px-3 py-1.5 border rounded-md text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Role Field */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Role</label>
|
||||
<select
|
||||
name="role"
|
||||
value={form.role}
|
||||
onChange={handleChange}
|
||||
className="w-full px-3 py-1.5 border rounded-md text-sm"
|
||||
>
|
||||
<option value="member">Member</option>
|
||||
<option value="admin">Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Join Date Field */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Join Date</label>
|
||||
<input
|
||||
type="date"
|
||||
name="joinDate"
|
||||
value={form.joinDate}
|
||||
onChange={handleChange}
|
||||
className="w-full px-3 py-1.5 border rounded-md text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full bg-blue-600 text-white py-1.5 px-4 rounded-md hover:bg-blue-700 transition text-sm"
|
||||
>
|
||||
Add Member
|
||||
</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
@ -6,24 +6,87 @@ import { databaseId, membersCollectionId } from "../lib/appwrite";
|
||||
import { getCurrentUserWithRole } from "../utils/auth";
|
||||
import { Query } from "appwrite";
|
||||
|
||||
// Import icons
|
||||
const EditIcon = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const DeleteIcon = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const SaveIcon = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const CancelIcon = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clipRule="evenodd" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// Confirmation Modal Component
|
||||
const ConfirmationModal = ({ isOpen, onClose, onConfirm, title, message }) => {
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-white rounded-lg shadow-xl max-w-md w-full">
|
||||
<div className="p-6">
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">{title}</h3>
|
||||
<p className="text-gray-600 mb-6">{message}</p>
|
||||
|
||||
<div className="flex justify-end space-x-3">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 border border-gray-300 rounded-md text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={onConfirm}
|
||||
className="px-4 py-2 bg-red-600 text-white rounded-md text-sm font-medium hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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 <div className="p-4">Loading members...</div>;
|
||||
}
|
||||
@ -50,7 +183,6 @@ export default function MemberList() {
|
||||
<div className="mt-8 p-4">
|
||||
<h2 className="text-xl font-semibold mb-4">All Members</h2>
|
||||
|
||||
{/* Current user info */}
|
||||
{currentUser && (
|
||||
<div className="mb-6 p-4 bg-blue-50 rounded-lg">
|
||||
<p className="font-medium">You are logged in as: {currentUser.name}</p>
|
||||
@ -58,7 +190,6 @@ export default function MemberList() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Members table */}
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full border">
|
||||
<thead>
|
||||
@ -68,32 +199,115 @@ export default function MemberList() {
|
||||
<th className="p-3">Phone</th>
|
||||
<th className="p-3">Join Date</th>
|
||||
<th className="p-3">Role</th>
|
||||
{currentUser?.role === 'admin' && <th className="p-3">Actions</th>}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{members.length > 0 ? (
|
||||
members.map((member) => (
|
||||
<tr key={member.$id} className="border-t hover:bg-gray-50">
|
||||
<td className="p-3">{member.name || "N/A"}</td>
|
||||
<td className="p-3">{member.email || "N/A"}</td>
|
||||
<td className="p-3">{member.phone || "N/A"}</td>
|
||||
<td className="p-3">
|
||||
{member.joinDate ? new Date(member.joinDate).toLocaleDateString() : "N/A"}
|
||||
</td>
|
||||
<td className="p-3 capitalize">
|
||||
<span className={`px-2 py-1 rounded-full text-xs ${
|
||||
member.role === "admin"
|
||||
? "bg-purple-100 text-purple-800"
|
||||
: "bg-blue-100 text-blue-800"
|
||||
}`}>
|
||||
{member.role || "unknown"}
|
||||
</span>
|
||||
</td>
|
||||
{editingId === member.$id ? (
|
||||
<>
|
||||
<td className="p-3">
|
||||
<input
|
||||
type="text"
|
||||
name="name"
|
||||
value={editFormData.name}
|
||||
onChange={handleEditFormChange}
|
||||
className="border p-1 w-full"
|
||||
/>
|
||||
</td>
|
||||
<td className="p-3">
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
value={editFormData.email}
|
||||
onChange={handleEditFormChange}
|
||||
className="border p-1 w-full"
|
||||
/>
|
||||
</td>
|
||||
<td className="p-3">
|
||||
<input
|
||||
type="text"
|
||||
name="phone"
|
||||
value={editFormData.phone}
|
||||
onChange={handleEditFormChange}
|
||||
className="border p-1 w-full"
|
||||
/>
|
||||
</td>
|
||||
<td className="p-3">
|
||||
{member.joinDate ? new Date(member.joinDate).toLocaleDateString() : "N/A"}
|
||||
</td>
|
||||
<td className="p-3">
|
||||
<select
|
||||
name="role"
|
||||
value={editFormData.role}
|
||||
onChange={handleEditFormChange}
|
||||
className="border p-1 w-full"
|
||||
>
|
||||
<option value="admin">Admin</option>
|
||||
<option value="member">Member</option>
|
||||
</select>
|
||||
</td>
|
||||
<td className="p-3 flex space-x-2">
|
||||
<button
|
||||
onClick={() => handleUpdate(member.$id)}
|
||||
className="bg-green-500 text-white p-1.5 rounded hover:bg-green-600 transition"
|
||||
title="Save"
|
||||
>
|
||||
<SaveIcon />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setEditingId(null)}
|
||||
className="bg-gray-500 text-white p-1.5 rounded hover:bg-gray-600 transition"
|
||||
title="Cancel"
|
||||
>
|
||||
<CancelIcon />
|
||||
</button>
|
||||
</td>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<td className="p-3">{member.name || "N/A"}</td>
|
||||
<td className="p-3">{member.email || "N/A"}</td>
|
||||
<td className="p-3">{member.phone || "N/A"}</td>
|
||||
<td className="p-3">
|
||||
{member.joinDate ? new Date(member.joinDate).toLocaleDateString() : "N/A"}
|
||||
</td>
|
||||
<td className="p-3 capitalize">
|
||||
<span className={`px-2 py-1 rounded-full text-xs ${
|
||||
member.role === "admin"
|
||||
? "bg-purple-100 text-purple-800"
|
||||
: "bg-blue-100 text-blue-800"
|
||||
}`}>
|
||||
{member.role || "unknown"}
|
||||
</span>
|
||||
</td>
|
||||
{currentUser?.role === 'admin' && (
|
||||
<td className="p-3 flex space-x-2">
|
||||
<button
|
||||
onClick={() => handleEditClick(member)}
|
||||
className="text-blue-500 hover:text-blue-700 p-1.5 rounded hover:bg-blue-50 transition"
|
||||
title="Edit"
|
||||
>
|
||||
<EditIcon />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDeleteClick(member.$id)}
|
||||
className="text-red-500 hover:text-red-700 p-1.5 rounded hover:bg-red-50 transition"
|
||||
title="Delete"
|
||||
>
|
||||
<DeleteIcon />
|
||||
</button>
|
||||
</td>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan="5" className="p-4 text-center text-gray-500">
|
||||
<td colSpan={currentUser?.role === 'admin' ? 6 : 5} className="p-4 text-center text-gray-500">
|
||||
No members found
|
||||
</td>
|
||||
</tr>
|
||||
@ -101,6 +315,14 @@ export default function MemberList() {
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<ConfirmationModal
|
||||
isOpen={showDeleteModal}
|
||||
onClose={() => setShowDeleteModal(false)}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
title="Delete Member"
|
||||
message="Are you sure you want to delete this member? This action cannot be undone."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -62,7 +62,7 @@ export default function Navbar({ toggleSidebar }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 px-4 py-3 sm:px-6 lg:px-8 relative">
|
||||
<nav className="bg-white border-b border-gray-200 dark:border-gray-700 px-4 py-3 sm:px-6 lg:px-8 relative">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center space-x-4">
|
||||
{/* Sidebar Toggle Button - Hidden on mobile */}
|
||||
|
@ -1,5 +1,5 @@
|
||||
'use client';
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { account, ID } from "../lib/appwrite";
|
||||
|
||||
@ -9,60 +9,129 @@ export default function AuthPage() {
|
||||
const [password, setPassword] = useState('');
|
||||
const [err, setErr] = useState('');
|
||||
const [isLogin, setIsLogin] = useState(true);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
// Check for existing session on component mount
|
||||
useEffect(() => {
|
||||
const checkSession = async () => {
|
||||
try {
|
||||
const session = await account.getSession('current');
|
||||
if (session) {
|
||||
router.push('/pages/dashboard');
|
||||
}
|
||||
} catch (error) {
|
||||
// No active session
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
checkSession();
|
||||
}, [router]);
|
||||
|
||||
const handleAuth = async (e) => {
|
||||
e.preventDefault();
|
||||
setErr('');
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
if (isLogin) {
|
||||
await account.createEmailPasswordSession(email, password);
|
||||
router.push('/pages/dashboard');
|
||||
} else {
|
||||
await account.create(ID.unique(), email, password);
|
||||
// Automatically log in after sign up
|
||||
await account.createEmailPasswordSession(email, password);
|
||||
}
|
||||
|
||||
// Verify session was created successfully
|
||||
const session = await account.getSession('current');
|
||||
if (session) {
|
||||
router.push('/pages/dashboard');
|
||||
} else {
|
||||
setErr('Authentication failed. Please try again.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Auth error:', error);
|
||||
setErr(error.message || 'Something went wrong');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-center">
|
||||
<p>Checking authentication status...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<form onSubmit={handleAuth} className="bg-white p-6 rounded shadow-md w-full max-w-sm">
|
||||
<h2 className="text-xl font-semibold mb-4">{isLogin ? 'Login' : 'Sign Up'}</h2>
|
||||
{err && <p className="text-red-500 mb-4">{err}</p>}
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
className="w-full mb-2 p-2 border rounded"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
className="w-full mb-4 p-2 border rounded"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
minLength="8"
|
||||
/>
|
||||
<button type="submit" className="bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded w-full mb-4">
|
||||
{isLogin ? 'Login' : 'Sign Up'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setIsLogin(!isLogin);
|
||||
setErr('');
|
||||
}}
|
||||
className="text-blue-500 hover:text-blue-700 text-sm"
|
||||
<div className="flex items-center justify-center min-h-screen bg-gray-50">
|
||||
<form onSubmit={handleAuth} className="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
|
||||
<h2 className="text-2xl font-bold text-center mb-6 text-gray-800">
|
||||
{isLogin ? 'Login' : 'Create Account'}
|
||||
</h2>
|
||||
|
||||
{err && (
|
||||
<div className="mb-4 p-3 bg-red-100 text-red-700 rounded-md text-sm">
|
||||
{err}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mb-4">
|
||||
<label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Email Address
|
||||
</label>
|
||||
<input
|
||||
id="email"
|
||||
type="email"
|
||||
placeholder="your@email.com"
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
required
|
||||
autoComplete="email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="mb-6">
|
||||
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
|
||||
Password
|
||||
</label>
|
||||
<input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="••••••••"
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
required
|
||||
minLength="8"
|
||||
autoComplete={isLogin ? "current-password" : "new-password"}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className={`w-full py-2 px-4 rounded-md text-white font-medium ${isLoading ? 'bg-blue-400 cursor-not-allowed' : 'bg-blue-600 hover:bg-blue-700'}`}
|
||||
>
|
||||
{isLogin ? 'Need an account? Sign Up' : 'Already have an account? Login'}
|
||||
{isLoading ? 'Processing...' : (isLogin ? 'Login' : 'Sign Up')}
|
||||
</button>
|
||||
|
||||
<div className="mt-4 text-center text-sm text-gray-600">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setIsLogin(!isLogin);
|
||||
setErr('');
|
||||
}}
|
||||
className="text-blue-600 hover:text-blue-800 focus:outline-none"
|
||||
>
|
||||
{isLogin ? 'Need an account? Sign up' : 'Already have an account? Log in'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
@ -9,14 +9,25 @@ export default function Home() {
|
||||
useEffect(() => {
|
||||
const checkSession = async () => {
|
||||
try {
|
||||
await account.get(); // Check if session exists
|
||||
router.replace('/pages/dashboard'); // Redirect if logged in
|
||||
} catch {
|
||||
router.replace('/login'); // Redirect to login if no session
|
||||
const session = await account.getSession('current');
|
||||
if (session) {
|
||||
router.replace('/pages/dashboard');
|
||||
} else {
|
||||
router.replace('/login');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Session check error:', error);
|
||||
router.replace('/login');
|
||||
}
|
||||
};
|
||||
checkSession();
|
||||
}, []);
|
||||
}, [router]);
|
||||
|
||||
return null; // No content, just redirection
|
||||
}
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-center">
|
||||
<p>Checking authentication status...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user