diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 9a5aced..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,139 +0,0 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-lerna-debug.log*
-
-# Diagnostic reports (https://nodejs.org/api/report.html)
-report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
-
-# Runtime data
-pids
-*.pid
-*.seed
-*.pid.lock
-
-# Directory for instrumented libs generated by jscoverage/JSCover
-lib-cov
-
-# Coverage directory used by tools like istanbul
-coverage
-*.lcov
-
-# nyc test coverage
-.nyc_output
-
-# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
-.grunt
-
-# Bower dependency directory (https://bower.io/)
-bower_components
-
-# node-waf configuration
-.lock-wscript
-
-# Compiled binary addons (https://nodejs.org/api/addons.html)
-build/Release
-
-# Dependency directories
-node_modules/
-jspm_packages/
-
-# Snowpack dependency directory (https://snowpack.dev/)
-web_modules/
-
-# TypeScript cache
-*.tsbuildinfo
-
-# Optional npm cache directory
-.npm
-
-# Optional eslint cache
-.eslintcache
-
-# Optional stylelint cache
-.stylelintcache
-
-# Optional REPL history
-.node_repl_history
-
-# Output of 'npm pack'
-*.tgz
-
-# Yarn Integrity file
-.yarn-integrity
-
-# dotenv environment variable files
-.env
-.env.*
-!.env.example
-
-# parcel-bundler cache (https://parceljs.org/)
-.cache
-.parcel-cache
-
-# Next.js build output
-.next
-out
-
-# Nuxt.js build / generate output
-.nuxt
-dist
-
-# Gatsby files
-.cache/
-# Comment in the public line in if your project uses Gatsby and not Next.js
-# https://nextjs.org/blog/next-9-1#public-directory-support
-# public
-
-# vuepress build output
-.vuepress/dist
-
-# vuepress v2.x temp and cache directory
-.temp
-.cache
-
-# Sveltekit cache directory
-.svelte-kit/
-
-# vitepress build output
-**/.vitepress/dist
-
-# vitepress cache directory
-**/.vitepress/cache
-
-# Docusaurus cache and generated files
-.docusaurus
-
-# Serverless directories
-.serverless/
-
-# FuseBox cache
-.fusebox/
-
-# DynamoDB Local files
-.dynamodb/
-
-# Firebase cache directory
-.firebase/
-
-# TernJS port file
-.tern-port
-
-# Stores VSCode versions used for testing VSCode extensions
-.vscode-test
-
-# yarn v3
-.pnp.*
-.yarn/*
-!.yarn/patches
-!.yarn/plugins
-!.yarn/releases
-!.yarn/sdks
-!.yarn/versions
-
-# Vite logs files
-vite.config.js.timestamp-*
-vite.config.ts.timestamp-*
diff --git a/About.html b/About.html
deleted file mode 100644
index ba0cbbc..0000000
--- a/About.html
+++ /dev/null
@@ -1,112 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- Register Organization
-
-
-
-
-
-
-
-
Recruit Verify
-
-
- Volun -teer
-
-
-
for Your Events
-
-
- Create and manage volunteers, connect with verified volunteers,
- track attendance, and reward effort with certificates — all in one
- platform designed to maximize your community impact.
-
-
-
- Register Organization
- Learn More
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Admin/certificates/certificate-preview.css b/Admin/certificates/certificate-preview.css
new file mode 100644
index 0000000..59e84b6
--- /dev/null
+++ b/Admin/certificates/certificate-preview.css
@@ -0,0 +1,170 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #edf2f6;
+ color: #172033;
+}
+
+.overlay {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1.5rem;
+}
+
+.modal {
+ width: 100%;
+ max-width: 34rem;
+ background: #ffffff;
+ border-radius: 1rem;
+ padding: 1.8rem;
+ position: relative;
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.12);
+}
+
+.close-btn {
+ position: absolute;
+ top: 1rem;
+ right: 1rem;
+ width: 2rem;
+ height: 2rem;
+ border: none;
+ background: transparent;
+ color: #6b7280;
+ font-size: 1rem;
+ cursor: pointer;
+}
+
+.close-btn:hover {
+ color: #172033;
+}
+
+.modal h1 {
+ font-size: 1.6rem;
+ font-weight: 700;
+ color: #172033;
+ margin-bottom: 1.5rem;
+}
+
+.details-grid {
+ display: grid;
+ grid-template-columns: 8rem 1fr;
+ gap: 0.9rem 1rem;
+ margin-bottom: 1.5rem;
+}
+
+.label {
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: #223f6b;
+}
+
+.value {
+ font-size: 0.9rem;
+ color: #374151;
+ word-break: break-word;
+}
+
+.certificate-card {
+ margin-top: 1rem;
+ border: 0.0625rem solid #d9dde3;
+ border-radius: 0.8rem;
+ padding: 1.4rem 1rem;
+ text-align: center;
+ background: #f9fbfd;
+}
+
+.certificate-icon {
+ position: relative;
+ width: 4rem;
+ height: 4rem;
+ margin: 0 auto 0.8rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #1d7de2;
+ font-size: 2rem;
+}
+
+.badge-icon {
+ position: absolute;
+ bottom: -0.2rem;
+ right: -0.2rem;
+ color: #f2a10d;
+ font-size: 1.2rem;
+}
+
+.certificate-card p {
+ font-size: 0.92rem;
+ font-weight: 700;
+ color: #16a34a;
+ letter-spacing: 0.04em;
+}
+
+.feedback {
+ min-height: 1.2rem;
+ font-size: 0.85rem;
+ margin-top: 1rem;
+ text-align: center;
+}
+
+.feedback.success {
+ color: #16a34a;
+}
+
+.feedback.error {
+ color: #dc2626;
+}
+
+.action-row {
+ margin-top: 1rem;
+ display: flex;
+ justify-content: center;
+}
+
+.action-btn {
+ border: none;
+ padding: 0.9rem 1.2rem;
+ border-radius: 0.5rem;
+ font-size: 0.9rem;
+ cursor: pointer;
+}
+
+.secondary-btn {
+ background: #223f6b;
+ color: #ffffff;
+}
+
+.secondary-btn:hover {
+ opacity: 0.94;
+}
+
+.secondary-btn:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+}
+
+@media (max-width: 36rem) {
+ .modal {
+ padding: 1.2rem;
+ }
+
+ .details-grid {
+ grid-template-columns: 1fr;
+ gap: 0.4rem;
+ }
+
+ .label {
+ margin-top: 0.4rem;
+ }
+
+ .action-btn {
+ width: 100%;
+ }
+}
\ No newline at end of file
diff --git a/Admin/certificates/certificate-preview.html b/Admin/certificates/certificate-preview.html
new file mode 100644
index 0000000..a6a53b8
--- /dev/null
+++ b/Admin/certificates/certificate-preview.html
@@ -0,0 +1,60 @@
+
+
+
+
+
+ Certificate Preview
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Certificate Preview
+
+
+
Volunteer Name:
+
Loading...
+
+
Event Name:
+
Loading...
+
+
Phone No:
+
—
+
+
Org. Name:
+
—
+
+
Events Date:
+
—
+
+
+
+
+
+
+
+
+ Download Certificate
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Admin/certificates/certificate-preview.js b/Admin/certificates/certificate-preview.js
new file mode 100644
index 0000000..b1924b6
--- /dev/null
+++ b/Admin/certificates/certificate-preview.js
@@ -0,0 +1,157 @@
+const API_BASE_URL = "https://aidloop-backend.onrender.com/api";
+
+const els = {
+ closeBtn: document.getElementById("closeBtn"),
+ volunteerName: document.getElementById("volunteerName"),
+ eventName: document.getElementById("eventName"),
+ phoneNumber: document.getElementById("phoneNumber"),
+ organizerName: document.getElementById("organizerName"),
+ eventDate: document.getElementById("eventDate"),
+ certificateStatus: document.getElementById("certificateStatus"),
+ feedback: document.getElementById("feedback"),
+ downloadBtn: document.getElementById("downloadBtn")
+};
+
+const certificateId = new URLSearchParams(window.location.search).get("id");
+
+async function apiRequest(endpoint, options = {}) {
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
+ credentials: "include",
+ headers: {
+ ...(options.headers || {})
+ },
+ ...options
+ });
+
+ const contentType = response.headers.get("content-type") || "";
+ const data = contentType.includes("application/json")
+ ? await response.json()
+ : await response.blob();
+
+ if (!response.ok) {
+ if (contentType.includes("application/json")) {
+ throw new Error(data.message || data.error || "Request failed");
+ }
+ throw new Error("Request failed");
+ }
+
+ return data;
+}
+
+function formatDate(dateValue) {
+ if (!dateValue) return "—";
+ const date = new Date(dateValue);
+ if (Number.isNaN(date.getTime())) return dateValue;
+ return date.toLocaleDateString("en-GB", {
+ day: "2-digit",
+ month: "long",
+ year: "numeric"
+ });
+}
+
+function setFeedback(message, type = "") {
+ els.feedback.textContent = message;
+ els.feedback.className = "feedback";
+ if (type) {
+ els.feedback.classList.add(type);
+ }
+}
+
+function populateCertificate(data) {
+ els.volunteerName.textContent =
+ data.user?.fullName ||
+ data.user?.name ||
+ data.volunteer?.fullName ||
+ data.volunteer?.name ||
+ data.volunteerName ||
+ "—";
+
+ els.eventName.textContent =
+ data.event?.name ||
+ data.eventName ||
+ "—";
+
+ els.phoneNumber.textContent =
+ data.user?.phoneNumber ||
+ data.user?.phone ||
+ data.volunteer?.phoneNumber ||
+ data.volunteer?.phone ||
+ data.phoneNumber ||
+ "—";
+
+ els.organizerName.textContent =
+ data.organizer?.fullName ||
+ data.organizer?.name ||
+ data.event?.organizer?.fullName ||
+ data.event?.organizer?.name ||
+ data.organizerName ||
+ "—";
+
+ els.eventDate.textContent = formatDate(
+ data.event?.date ||
+ data.eventDate ||
+ data.issuedAt ||
+ data.createdAt
+ );
+
+ const status = String(data.status || "issued").toUpperCase();
+ els.certificateStatus.textContent = status;
+}
+
+async function loadCertificate() {
+ if (!certificateId) {
+ setFeedback("No certificate ID provided.", "error");
+ els.downloadBtn.disabled = true;
+ return;
+ }
+
+ try {
+ const data = await apiRequest(`/certificates/verify/${certificateId}`);
+ populateCertificate(data);
+ setFeedback("Certificate loaded successfully.", "success");
+ } catch (error) {
+ setFeedback(error.message || "Failed to load certificate.", "error");
+ els.downloadBtn.disabled = true;
+ }
+}
+
+async function downloadCertificate() {
+ if (!certificateId) return;
+
+ try {
+ els.downloadBtn.disabled = true;
+ els.downloadBtn.textContent = "Downloading...";
+
+ const blob = await apiRequest(`/certificates/download/${certificateId}`);
+ const url = URL.createObjectURL(blob);
+
+ const link = document.createElement("a");
+ link.href = url;
+ link.download = `certificate-${certificateId}.pdf`;
+ document.body.appendChild(link);
+ link.click();
+ link.remove();
+
+ URL.revokeObjectURL(url);
+ setFeedback("Certificate downloaded successfully.", "success");
+ } catch (error) {
+ setFeedback(error.message || "Failed to download certificate.", "error");
+ } finally {
+ els.downloadBtn.disabled = false;
+ els.downloadBtn.textContent = "Download Certificate";
+ }
+}
+
+function closePreview() {
+ if (window.history.length > 1) {
+ window.history.back();
+ return;
+ }
+
+ window.location.href = "certificates.html";
+}
+
+els.closeBtn.addEventListener("click", closePreview);
+els.downloadBtn.addEventListener("click", downloadCertificate);
+
+document.addEventListener("DOMContentLoaded", loadCertificate);
\ No newline at end of file
diff --git a/Admin/certificates/certificates.css b/Admin/certificates/certificates.css
new file mode 100644
index 0000000..561f437
--- /dev/null
+++ b/Admin/certificates/certificates.css
@@ -0,0 +1,421 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #edf2f6;
+ color: #172033;
+}
+
+.dashboard {
+ display: flex;
+ min-height: 100vh;
+}
+
+.sidebar {
+ width: 15rem;
+ background: #223f6b;
+ color: #ffffff;
+ padding: 1.8rem 0.9rem;
+}
+
+.sidebar-logo {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.35rem;
+ margin-bottom: 2.4rem;
+}
+
+.sidebar-logo img {
+ width: 3.1rem;
+ height: 3.1rem;
+ object-fit: contain;
+}
+
+.sidebar-menu {
+ list-style: none;
+}
+
+.sidebar-menu li {
+ margin-bottom: 0.8rem;
+}
+
+.sidebar-menu li a,
+.logout-btn {
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+ text-decoration: none;
+ color: #ffffff;
+ padding: 0.9rem 0.85rem;
+ border-radius: 0.45rem;
+ font-size: 0.95rem;
+ transition: background 0.2s ease;
+ width: 100%;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+}
+
+.sidebar-menu li a i,
+.logout-btn i {
+ width: 1rem;
+ text-align: center;
+}
+
+.sidebar-menu li.active a,
+.sidebar-menu li a:hover {
+ background: #1d7de2;
+}
+
+.logout-btn:hover {
+ background: #ef2f2f;
+}
+
+.main-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.topbar {
+ height: 5.8rem;
+ background: #ffffff;
+ border-bottom: 0.0625rem solid #e6e8eb;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 1.8rem;
+}
+
+.search-box {
+ width: 21rem;
+ height: 2.9rem;
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ border: 0.0625rem solid #d9dde3;
+ border-radius: 0.35rem;
+ padding: 0 0.9rem;
+ background: #ffffff;
+}
+
+.search-box i {
+ color: #6b7280;
+ font-size: 0.95rem;
+}
+
+.search-box input {
+ width: 100%;
+ border: none;
+ outline: none;
+ background: transparent;
+ font-size: 0.95rem;
+ color: #374151;
+}
+
+.topbar-right {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.notification {
+ position: relative;
+ color: #111827;
+ font-size: 1.2rem;
+}
+
+.notification-dot {
+ position: absolute;
+ top: 0.08rem;
+ right: -0.12rem;
+ width: 0.45rem;
+ height: 0.45rem;
+ background: #ef2f2f;
+ border-radius: 50%;
+}
+
+.admin-profile {
+ display: flex;
+ align-items: center;
+ gap: 0.7rem;
+}
+
+.user-avatar {
+ width: 2.45rem;
+ height: 2.45rem;
+ border-radius: 50%;
+ object-fit: cover;
+}
+
+.admin-text h4 {
+ font-size: 0.82rem;
+ font-weight: 600;
+ color: #172033;
+ line-height: 1.2;
+}
+
+.admin-text p {
+ font-size: 0.72rem;
+ color: #6b7280;
+ line-height: 1.2;
+}
+
+.admin-chevron {
+ color: #6b7280;
+ font-size: 0.7rem;
+}
+
+.page-content {
+ padding: 4rem 1.4rem 2rem;
+}
+
+.page-header {
+ margin-bottom: 2rem;
+}
+
+.page-header h1 {
+ font-size: 2.1rem;
+ font-weight: 700;
+ margin-bottom: 0.3rem;
+}
+
+.page-header p {
+ font-size: 0.78rem;
+ color: #6b7280;
+}
+
+.filter-buttons {
+ display: flex;
+ gap: 0.8rem;
+ margin-bottom: 1.4rem;
+ flex-wrap: wrap;
+}
+
+.filter-btn {
+ border: 0.0625rem solid #d9dde3;
+ background: #ffffff;
+ color: #374151;
+ padding: 0.75rem 1rem;
+ border-radius: 0.4rem;
+ font-size: 0.8rem;
+ cursor: pointer;
+}
+
+.filter-btn.active {
+ background: #1d7de2;
+ color: #ffffff;
+ border-color: #1d7de2;
+}
+
+.table-wrapper {
+ background: #ffffff;
+ overflow: hidden;
+ border-radius: 0.6rem;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+thead {
+ background: #f4f6f8;
+}
+
+th,
+td {
+ text-align: left;
+ padding: 1rem 1.4rem;
+}
+
+th {
+ font-size: 0.62rem;
+ letter-spacing: 0.08em;
+ color: #7b8492;
+ font-weight: 700;
+}
+
+td {
+ font-size: 0.72rem;
+ color: #172033;
+ border-top: 0.0625rem solid #eef1f3;
+}
+
+.action-link {
+ color: #1d7de2;
+ text-decoration: none;
+ font-weight: 600;
+}
+
+.action-link:hover {
+ text-decoration: underline;
+}
+
+.empty-state {
+ text-align: center;
+ padding: 3rem 1rem;
+}
+
+.empty-icon {
+ width: 4rem;
+ height: 4rem;
+ margin: 0 auto 1rem;
+ border-radius: 50%;
+ background: #e8f0fb;
+ color: #1d7de2;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.4rem;
+}
+
+.empty-state h2 {
+ font-size: 1.1rem;
+ margin-bottom: 0.4rem;
+}
+
+.empty-state p {
+ color: #6b7280;
+ font-size: 0.85rem;
+}
+
+/* Logout Modal */
+.hidden {
+ display: none !important;
+}
+
+.modal-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(15, 23, 42, 0.45);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1rem;
+ z-index: 999;
+}
+
+.modal-card {
+ width: 100%;
+ max-width: 25rem;
+ background: #ffffff;
+ border-radius: 1rem;
+ padding: 1.5rem;
+ position: relative;
+ text-align: center;
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.15);
+}
+
+.modal-close-btn {
+ position: absolute;
+ top: 0.7rem;
+ right: 0.9rem;
+ border: none;
+ background: transparent;
+ font-size: 1.5rem;
+ color: #6b7280;
+ cursor: pointer;
+}
+
+.modal-icon-wrap {
+ width: 3.5rem;
+ height: 3.5rem;
+ margin: 0 auto 1rem;
+ border-radius: 50%;
+ background: #fee2e2;
+ color: #dc2626;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.25rem;
+}
+
+.modal-card h3 {
+ font-size: 1.2rem;
+ margin-bottom: 0.5rem;
+ color: #172033;
+}
+
+.modal-card p {
+ font-size: 0.92rem;
+ color: #6b7280;
+ margin-bottom: 1.3rem;
+}
+
+.modal-actions {
+ display: flex;
+ gap: 0.8rem;
+ justify-content: center;
+}
+
+.modal-btn {
+ border: none;
+ border-radius: 0.55rem;
+ padding: 0.8rem 1rem;
+ font-size: 0.9rem;
+ cursor: pointer;
+ min-width: 8rem;
+}
+
+.modal-btn.secondary {
+ background: #e5e7eb;
+ color: #172033;
+}
+
+.modal-btn.danger {
+ background: #dc2626;
+ color: #ffffff;
+}
+
+@media (max-width: 75rem) {
+ .table-wrapper {
+ overflow-x: auto;
+ }
+
+ table {
+ min-width: 46rem;
+ }
+}
+
+@media (max-width: 62rem) {
+ .sidebar {
+ display: none;
+ }
+
+ .topbar {
+ padding: 0 1rem;
+ }
+
+ .search-box {
+ width: 16rem;
+ }
+
+ .page-content {
+ padding: 2rem 1rem;
+ }
+
+ .admin-text,
+ .admin-chevron {
+ display: none;
+ }
+}
+
+@media (max-width: 36rem) {
+ .search-box {
+ width: 12rem;
+ }
+
+ .modal-actions {
+ flex-direction: column;
+ }
+
+ .modal-btn {
+ width: 100%;
+ }
+}
\ No newline at end of file
diff --git a/Admin/certificates/certificates.html b/Admin/certificates/certificates.html
new file mode 100644
index 0000000..034e060
--- /dev/null
+++ b/Admin/certificates/certificates.html
@@ -0,0 +1,156 @@
+
+
+
+
+
+ AIDLoop - Certificates
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Status: All
+
+ Issued
+ Not Issued
+
+
+
+
+
+
+ VOLUNTEER NAME
+ EVENT NAME
+ ORGANIZER
+ DATE ISSUED
+ ACTIONS
+
+
+
+
+
+ Loading certificates...
+
+
+
+
+
+
+
+
+
No certificates found
+
No certificate records match the current filter.
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
Log out?
+
You are about to end your current admin session.
+
+
+ Cancel
+ Yes, Log out
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Admin/certificates/certificates.js b/Admin/certificates/certificates.js
new file mode 100644
index 0000000..dc1f000
--- /dev/null
+++ b/Admin/certificates/certificates.js
@@ -0,0 +1,261 @@
+const API_BASE_URL = "https://aidloop-backend.onrender.com/api";
+
+const els = {
+ adminName: document.getElementById("adminName"),
+ adminRole: document.getElementById("adminRole"),
+ adminAvatar: document.getElementById("adminAvatar"),
+ certificatesTable: document.getElementById("certificatesTable"),
+ certificatesTableWrap: document.getElementById("certificatesTableWrap"),
+ emptyState: document.getElementById("emptyState"),
+ searchInput: document.getElementById("searchInput"),
+ filterButtons: document.querySelectorAll(".filter-btn"),
+ logoutBtn: document.getElementById("logoutBtn"),
+ logoutModal: document.getElementById("logoutModal"),
+ closeLogoutModal: document.getElementById("closeLogoutModal"),
+ cancelLogout: document.getElementById("cancelLogout"),
+ confirmLogout: document.getElementById("confirmLogout")
+};
+
+let certificateRowsCache = [];
+let currentFilter = "all";
+
+async function apiRequest(endpoint, options = {}) {
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ ...(options.headers || {})
+ },
+ ...options
+ });
+
+ const contentType = response.headers.get("content-type") || "";
+ const data = contentType.includes("application/json")
+ ? await response.json()
+ : await response.blob();
+
+ if (!response.ok) {
+ if (contentType.includes("application/json")) {
+ throw new Error(data.message || data.error || "Request failed");
+ }
+ throw new Error("Request failed");
+ }
+
+ return data;
+}
+
+function normalizeCertificates(payload) {
+ if (Array.isArray(payload)) return payload;
+ if (Array.isArray(payload?.certificates)) return payload.certificates;
+ if (Array.isArray(payload?.data)) return payload.data;
+ return [];
+}
+
+function formatDate(dateValue) {
+ if (!dateValue) return "—";
+ const date = new Date(dateValue);
+ if (Number.isNaN(date.getTime())) return dateValue;
+ return date.toLocaleDateString("en-GB", {
+ day: "2-digit",
+ month: "short",
+ year: "numeric"
+ });
+}
+
+function getStatusValue(item) {
+ const raw = String(item.status || "").toLowerCase();
+ if (raw === "issued") return "issued";
+ return "issued";
+}
+
+function getVolunteerName(item) {
+ return (
+ item.user?.fullName ||
+ item.user?.name ||
+ item.volunteer?.fullName ||
+ item.volunteer?.name ||
+ item.volunteerName ||
+ "Volunteer"
+ );
+}
+
+function getEventName(item) {
+ return item.event?.name || item.eventName || "Event";
+}
+
+function getOrganizerName(item) {
+ return (
+ item.organizer?.fullName ||
+ item.organizer?.name ||
+ item.event?.organizer?.fullName ||
+ item.event?.organizer?.name ||
+ item.organizerName ||
+ "Organizer"
+ );
+}
+
+function getCertificateId(item) {
+ return item._id || item.id || item.certificateId || "";
+}
+
+function openLogoutModal() {
+ els.logoutModal.classList.remove("hidden");
+}
+
+function closeLogoutModal() {
+ els.logoutModal.classList.add("hidden");
+ els.confirmLogout.disabled = false;
+ els.confirmLogout.textContent = "Yes, Log out";
+}
+
+async function handleLogout() {
+ try {
+ els.confirmLogout.disabled = true;
+ els.confirmLogout.textContent = "Logging out...";
+
+ await apiRequest("/auth/logout", {
+ method: "POST"
+ });
+ } catch (error) {
+ console.warn("Logout failed:", error.message);
+ } finally {
+ localStorage.clear();
+ sessionStorage.clear();
+ window.location.href = "../../index.html";
+ }
+}
+
+function renderCertificates() {
+ const query = els.searchInput.value.trim().toLowerCase();
+
+ let filtered = [...certificateRowsCache];
+
+ if (currentFilter !== "all") {
+ filtered = filtered.filter((item) => {
+ const status = getStatusValue(item);
+ return currentFilter === "issued"
+ ? status === "issued"
+ : status !== "issued";
+ });
+ }
+
+ if (query) {
+ filtered = filtered.filter((item) => {
+ const searchableText = `
+ ${getVolunteerName(item)}
+ ${getEventName(item)}
+ ${getOrganizerName(item)}
+ ${formatDate(item.issuedAt || item.createdAt || item.date)}
+ `.toLowerCase();
+
+ return searchableText.includes(query);
+ });
+ }
+
+ if (!filtered.length) {
+ els.certificatesTableWrap.style.display = "none";
+ els.emptyState.style.display = "block";
+ return;
+ }
+
+ els.certificatesTableWrap.style.display = "table";
+ els.emptyState.style.display = "none";
+
+ els.certificatesTable.innerHTML = filtered.map((item) => {
+ const certificateId = getCertificateId(item);
+
+ return `
+
+ ${getVolunteerName(item)}
+ ${getEventName(item)}
+ ${getOrganizerName(item)}
+ ${formatDate(item.issuedAt || item.createdAt || item.date)}
+
+
+ View Certificate
+
+
+
+ `;
+ }).join("");
+}
+
+async function loadAdminProfile() {
+ try {
+ let profile;
+ try {
+ profile = await apiRequest("/users/me");
+ } catch {
+ profile = await apiRequest("/user/me");
+ }
+
+ els.adminName.textContent =
+ profile.fullName ||
+ profile.name ||
+ "Admin User";
+
+ els.adminRole.textContent =
+ profile.role
+ ? profile.role.charAt(0).toUpperCase() + profile.role.slice(1)
+ : "Admin";
+
+ if (profile.profileImage) {
+ els.adminAvatar.src = profile.profileImage;
+ }
+ } catch (error) {
+ console.error("Failed to load admin profile:", error.message);
+ window.location.href = "../profile/admin-profile.html";
+ }
+}
+
+async function loadCertificates() {
+ try {
+ const payload = await apiRequest("/certificates/my-certificates");
+ certificateRowsCache = normalizeCertificates(payload);
+ renderCertificates();
+ } catch (error) {
+ console.error("Failed to load certificates:", error.message);
+ certificateRowsCache = [];
+ renderCertificates();
+ }
+}
+
+function bindFilters() {
+ els.filterButtons.forEach((button) => {
+ button.addEventListener("click", () => {
+ els.filterButtons.forEach((btn) => btn.classList.remove("active"));
+ button.classList.add("active");
+ currentFilter = button.dataset.filter;
+ renderCertificates();
+ });
+ });
+}
+
+function bindUI() {
+ els.searchInput.addEventListener("input", renderCertificates);
+
+ bindFilters();
+
+ els.logoutBtn.addEventListener("click", openLogoutModal);
+ els.closeLogoutModal.addEventListener("click", closeLogoutModal);
+ els.cancelLogout.addEventListener("click", closeLogoutModal);
+ els.confirmLogout.addEventListener("click", handleLogout);
+
+ els.logoutModal.addEventListener("click", (event) => {
+ if (event.target === els.logoutModal) {
+ closeLogoutModal();
+ }
+ });
+
+ document.addEventListener("keydown", (event) => {
+ if (event.key === "Escape" && !els.logoutModal.classList.contains("hidden")) {
+ closeLogoutModal();
+ }
+ });
+}
+
+document.addEventListener("DOMContentLoaded", async () => {
+ bindUI();
+ await loadAdminProfile();
+ await loadCertificates();
+});
\ No newline at end of file
diff --git a/Admin/dashboard/admin-dashboard.css b/Admin/dashboard/admin-dashboard.css
new file mode 100644
index 0000000..e5a23e6
--- /dev/null
+++ b/Admin/dashboard/admin-dashboard.css
@@ -0,0 +1,520 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #edf2f6;
+ color: #172033;
+}
+
+.dashboard-layout {
+ display: flex;
+ min-height: 100vh;
+}
+
+/* Sidebar */
+.sidebar {
+ width: 15rem;
+ background: #223f6b;
+ color: #ffffff;
+ padding: 1.8rem 0.9rem;
+}
+
+.sidebar-logo {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.35rem;
+ margin-bottom: 2.4rem;
+}
+
+.sidebar-logo img {
+ width: 3.1rem;
+ height: 3.1rem;
+ object-fit: contain;
+}
+
+.sidebar-logo h2 {
+ font-size: 1.25rem;
+ font-weight: 700;
+ color: #1d7de2;
+}
+
+.sidebar-logo h2 span {
+ color: #27a657;
+}
+
+.sidebar-menu {
+ list-style: none;
+}
+
+.sidebar-menu li {
+ margin-bottom: 0.8rem;
+}
+
+.sidebar-menu li a {
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+ text-decoration: none;
+ color: #ffffff;
+ padding: 0.9rem 0.85rem;
+ border-radius: 0.45rem;
+ font-size: 0.95rem;
+ transition: background 0.2s ease;
+}
+
+.sidebar-menu li a i {
+ width: 1rem;
+ text-align: center;
+}
+
+.sidebar-menu li.active a,
+.sidebar-menu li a:hover {
+ background: #1d7de2;
+}
+
+/* Logout Button */
+.logout-btn {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+ border: none;
+ background: transparent;
+ color: #ffffff;
+ padding: 0.9rem 0.85rem;
+ border-radius: 0.45rem;
+ font-size: 0.95rem;
+ cursor: pointer;
+ transition: background 0.2s ease;
+}
+
+.logout-btn i {
+ width: 1rem;
+ text-align: center;
+}
+
+.logout-btn:hover {
+ background: #ef2f2f;
+}
+
+/* Main */
+.main-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+/* Topbar */
+.topbar {
+ height: 5.8rem;
+ background: #ffffff;
+ border-bottom: 0.0625rem solid #e6e8eb;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 1.8rem;
+}
+
+.search-box {
+ width: 21rem;
+ height: 2.9rem;
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ border: 0.0625rem solid #d9dde3;
+ border-radius: 0.35rem;
+ padding: 0 0.9rem;
+ background: #ffffff;
+}
+
+.search-box i {
+ color: #6b7280;
+ font-size: 0.95rem;
+}
+
+.search-box input {
+ width: 100%;
+ border: none;
+ outline: none;
+ background: transparent;
+ font-size: 0.95rem;
+ color: #374151;
+}
+
+.topbar-right {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.notification {
+ position: relative;
+ color: #111827;
+ font-size: 1.2rem;
+}
+
+.notification-dot {
+ position: absolute;
+ top: 0.08rem;
+ right: -0.12rem;
+ width: 0.45rem;
+ height: 0.45rem;
+ background: #ef2f2f;
+ border-radius: 50%;
+}
+
+.admin-profile {
+ display: flex;
+ align-items: center;
+ gap: 0.7rem;
+}
+
+.user-avatar {
+ width: 2.45rem;
+ height: 2.45rem;
+ border-radius: 50%;
+ object-fit: cover;
+}
+
+.admin-text h4 {
+ font-size: 0.82rem;
+ font-weight: 600;
+ color: #172033;
+ line-height: 1.2;
+}
+
+.admin-text p {
+ font-size: 0.72rem;
+ color: #6b7280;
+ line-height: 1.2;
+}
+
+.admin-chevron {
+ color: #6b7280;
+ font-size: 0.7rem;
+}
+
+/* Content */
+.page-content {
+ padding: 4rem 1.4rem 2rem;
+}
+
+.page-header {
+ margin-bottom: 2rem;
+}
+
+.page-header h1 {
+ font-size: 2.1rem;
+ font-weight: 700;
+ margin-bottom: 0.3rem;
+}
+
+.page-header p {
+ font-size: 0.78rem;
+ color: #6b7280;
+}
+
+/* Stats */
+.stats-grid {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 1.4rem;
+ margin-bottom: 2.8rem;
+ max-width: 40rem;
+}
+
+.stat-card {
+ background: #ffffff;
+ border: 0.0625rem solid #7f8ea5;
+ border-radius: 0.6rem;
+ overflow: hidden;
+ min-height: 5.8rem;
+}
+
+.stat-value {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 3rem;
+ font-size: 1.8rem;
+ font-weight: 700;
+ color: #111827;
+}
+
+.stat-footer {
+ height: 2.8rem;
+ border-top: 0.0625rem solid #b8c1cd;
+ display: flex;
+ align-items: center;
+ gap: 0.7rem;
+ padding: 0 1.1rem;
+ font-size: 0.65rem;
+ color: #172033;
+}
+
+.stat-footer i {
+ color: #1d7de2;
+ font-size: 1rem;
+}
+
+/* Section titles */
+.quick-actions-section h2,
+.recent-activity-section h2 {
+ font-size: 1.05rem;
+ font-weight: 700;
+ margin-bottom: 1.15rem;
+}
+
+/* Quick actions */
+.quick-actions-section {
+ margin-bottom: 3rem;
+ max-width: 42rem;
+}
+
+.quick-actions {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 2rem;
+}
+
+.quick-btn {
+ border: none;
+ background: #1d7de2;
+ color: #ffffff;
+ padding: 0.85rem 1rem;
+ border-radius: 0.2rem;
+ font-size: 0.65rem;
+ cursor: pointer;
+ transition: opacity 0.2s ease, transform 0.15s ease;
+}
+
+.quick-btn:hover {
+ opacity: 0.92;
+}
+
+.quick-btn:active {
+ transform: scale(0.98);
+}
+
+/* Recent activity */
+.recent-activity-section {
+ max-width: 42rem;
+}
+
+.table-wrapper {
+ background: #ffffff;
+ overflow: hidden;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+thead {
+ background: #f4f6f8;
+}
+
+th,
+td {
+ text-align: left;
+ padding: 1rem 1.4rem;
+}
+
+th {
+ font-size: 0.62rem;
+ letter-spacing: 0.08em;
+ color: #7b8492;
+ font-weight: 700;
+}
+
+td {
+ font-size: 0.68rem;
+ color: #172033;
+ border-top: 0.0625rem solid #eef1f3;
+}
+
+.status-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 4.7rem;
+ padding: 0.35rem 0.7rem;
+ border-radius: 0.45rem;
+ background: #f2a10d;
+ color: #ffffff;
+ font-size: 0.58rem;
+ font-weight: 700;
+}
+
+.status-badge.pending {
+ background: #f2a10d;
+ color: #fff;
+}
+
+.status-badge.published {
+ background: #16a34a;
+ color: #fff;
+}
+
+.status-badge.completed {
+ background: #2563eb;
+ color: #fff;
+}
+
+/* Logout Modal */
+.hidden {
+ display: none !important;
+}
+
+.modal-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(15, 23, 42, 0.45);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1rem;
+ z-index: 999;
+}
+
+.modal-card {
+ width: 100%;
+ max-width: 25rem;
+ background: #ffffff;
+ border-radius: 1rem;
+ padding: 1.5rem;
+ position: relative;
+ text-align: center;
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.15);
+}
+
+.modal-close-btn {
+ position: absolute;
+ top: 0.7rem;
+ right: 0.9rem;
+ border: none;
+ background: transparent;
+ font-size: 1.5rem;
+ color: #6b7280;
+ cursor: pointer;
+}
+
+.modal-icon-wrap {
+ width: 3.5rem;
+ height: 3.5rem;
+ margin: 0 auto 1rem;
+ border-radius: 50%;
+ background: #fee2e2;
+ color: #dc2626;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.25rem;
+}
+
+.modal-card h3 {
+ font-size: 1.2rem;
+ margin-bottom: 0.5rem;
+ color: #172033;
+}
+
+.modal-card p {
+ font-size: 0.92rem;
+ color: #6b7280;
+ margin-bottom: 1.3rem;
+}
+
+.modal-actions {
+ display: flex;
+ gap: 0.8rem;
+ justify-content: center;
+}
+
+.modal-btn {
+ border: none;
+ border-radius: 0.55rem;
+ padding: 0.8rem 1rem;
+ font-size: 0.9rem;
+ cursor: pointer;
+ min-width: 8rem;
+}
+
+.modal-btn.secondary {
+ background: #e5e7eb;
+ color: #172033;
+}
+
+.modal-btn.danger {
+ background: #dc2626;
+ color: #ffffff;
+}
+
+/* Responsive */
+@media (max-width: 75rem) {
+ .stats-grid,
+ .quick-actions {
+ grid-template-columns: repeat(2, 1fr);
+ max-width: none;
+ }
+
+ .recent-activity-section,
+ .quick-actions-section {
+ max-width: none;
+ }
+
+ .table-wrapper {
+ overflow-x: auto;
+ }
+
+ table {
+ min-width: 42rem;
+ }
+}
+
+@media (max-width: 62rem) {
+ .sidebar {
+ display: none;
+ }
+
+ .topbar {
+ padding: 0 1rem;
+ }
+
+ .search-box {
+ width: 16rem;
+ }
+
+ .page-content {
+ padding: 2rem 1rem;
+ }
+
+ .admin-text,
+ .admin-chevron {
+ display: none;
+ }
+}
+
+@media (max-width: 36rem) {
+ .stats-grid,
+ .quick-actions {
+ grid-template-columns: 1fr;
+ }
+
+ .search-box {
+ width: 12rem;
+ }
+
+ .modal-actions {
+ flex-direction: column;
+ }
+
+ .modal-btn {
+ width: 100%;
+ }
+}
\ No newline at end of file
diff --git a/Admin/dashboard/admin-dashboard.html b/Admin/dashboard/admin-dashboard.html
new file mode 100644
index 0000000..4b2cf57
--- /dev/null
+++ b/Admin/dashboard/admin-dashboard.html
@@ -0,0 +1,197 @@
+
+
+
+
+
+ AIDLoop Dashboard
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Quick Actions
+
+
+
+ Go to Verification Queue
+
+
+
+ View Organizations
+
+
+
+ View Events
+
+
+
+
+
+
+ Recent Activity
+
+
+
+
+
+ ACTIVITY
+ ENTITY
+ DATE
+ STATUS
+
+
+
+
+
+ Loading recent activity...
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
Log out?
+
You are about to end your current admin session.
+
+
+ Cancel
+ Yes, Log out
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Admin/dashboard/admin-dashboard.js b/Admin/dashboard/admin-dashboard.js
new file mode 100644
index 0000000..3c88cce
--- /dev/null
+++ b/Admin/dashboard/admin-dashboard.js
@@ -0,0 +1,316 @@
+const API_BASE_URL = "https://aidloop-backend.onrender.com/api";
+
+const els = {
+ adminName: document.getElementById("adminName"),
+ adminRole: document.getElementById("adminRole"),
+ adminAvatar: document.getElementById("adminAvatar"),
+ organizationCount: document.getElementById("organizationCount"),
+ pendingCount: document.getElementById("pendingCount"),
+ eventsCount: document.getElementById("eventsCount"),
+ activeUsersCount: document.getElementById("activeUsersCount"),
+ activityTable: document.getElementById("activityTable"),
+ searchInput: document.getElementById("searchInput"),
+ goVerificationQueue: document.getElementById("goVerificationQueue"),
+ viewOrganizations: document.getElementById("viewOrganizations"),
+ viewEvents: document.getElementById("viewEvents"),
+ logoutBtn: document.getElementById("logoutBtn"),
+ logoutModal: document.getElementById("logoutModal"),
+ closeLogoutModal: document.getElementById("closeLogoutModal"),
+ cancelLogout: document.getElementById("cancelLogout"),
+ confirmLogout: document.getElementById("confirmLogout")
+};
+
+let activityRowsCache = [];
+
+async function apiRequest(endpoint, options = {}) {
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ ...(options.headers || {})
+ },
+ ...options
+ });
+
+ const contentType = response.headers.get("content-type") || "";
+ const data = contentType.includes("application/json")
+ ? await response.json()
+ : await response.text();
+
+ if (!response.ok) {
+ throw new Error(
+ (data && data.message) ||
+ (data && data.error) ||
+ "Request failed"
+ );
+ }
+
+ return data;
+}
+
+function formatDate(dateValue) {
+ if (!dateValue) return "—";
+ const date = new Date(dateValue);
+ if (Number.isNaN(date.getTime())) return dateValue;
+ return date.toLocaleDateString("en-GB", {
+ day: "2-digit",
+ month: "short",
+ year: "numeric"
+ });
+}
+
+function setStatValue(element, target) {
+ const safeTarget = Number.isFinite(Number(target)) ? Number(target) : 0;
+ const duration = 600;
+ const steps = 24;
+ const increment = safeTarget / steps;
+ let current = 0;
+ let step = 0;
+
+ const timer = setInterval(() => {
+ step += 1;
+ current += increment;
+
+ if (step >= steps) {
+ element.textContent = safeTarget;
+ clearInterval(timer);
+ return;
+ }
+
+ element.textContent = Math.round(current);
+ }, duration / steps);
+}
+
+function normalizeUsers(usersPayload) {
+ if (Array.isArray(usersPayload)) return usersPayload;
+ if (Array.isArray(usersPayload?.users)) return usersPayload.users;
+ if (Array.isArray(usersPayload?.data)) return usersPayload.data;
+ return [];
+}
+
+function normalizeEvents(eventsPayload) {
+ if (Array.isArray(eventsPayload)) return eventsPayload;
+ if (Array.isArray(eventsPayload?.events)) return eventsPayload.events;
+ if (Array.isArray(eventsPayload?.data)) return eventsPayload.data;
+ return [];
+}
+
+function buildRecentActivity(users, events) {
+ const activities = [];
+
+ users.slice(0, 5).forEach((user) => {
+ const isOrganizer = String(user.role || "").toLowerCase() === "organizer";
+ const isPending =
+ String(user.status || "").toLowerCase() === "pending" ||
+ String(user.approvalStatus || "").toLowerCase() === "pending";
+
+ if (isOrganizer && isPending) {
+ activities.push({
+ activity: "Submitted for Verification",
+ entity: user.fullName || user.name || user.email || "Organizer",
+ date: user.createdAt || user.updatedAt,
+ status: "Pending"
+ });
+ }
+ });
+
+ events.slice(0, 5).forEach((event) => {
+ activities.push({
+ activity: "Event Created",
+ entity: event.name || "Untitled Event",
+ date: event.createdAt || event.date,
+ status: event.status || "Published"
+ });
+ });
+
+ return activities
+ .sort((a, b) => new Date(b.date || 0) - new Date(a.date || 0))
+ .slice(0, 8)
+ .map((item) => ({
+ ...item,
+ formattedDate: formatDate(item.date)
+ }));
+}
+
+function renderRecentActivity(rows) {
+ activityRowsCache = rows;
+ applyActivitySearch();
+}
+
+function getStatusBadgeClass(status) {
+ const normalized = String(status).toLowerCase();
+
+ if (normalized.includes("pending")) return "pending";
+ if (normalized.includes("published")) return "published";
+ if (normalized.includes("completed")) return "completed";
+ if (normalized.includes("approved")) return "published";
+ return "pending";
+}
+
+function applyActivitySearch() {
+ const query = els.searchInput.value.trim().toLowerCase();
+ const filtered = activityRowsCache.filter((row) =>
+ `${row.activity} ${row.entity} ${row.formattedDate} ${row.status}`
+ .toLowerCase()
+ .includes(query)
+ );
+
+ if (!filtered.length) {
+ els.activityTable.innerHTML = `
+
+ No matching activity found.
+
+ `;
+ return;
+ }
+
+ els.activityTable.innerHTML = filtered
+ .map(
+ (row) => `
+
+ ${row.activity}
+ ${row.entity}
+ ${row.formattedDate}
+
+
+ ${row.status}
+
+
+
+ `
+ )
+ .join("");
+}
+
+function openLogoutModal() {
+ els.logoutModal.classList.remove("hidden");
+}
+
+function closeLogoutModal() {
+ els.logoutModal.classList.add("hidden");
+ els.confirmLogout.disabled = false;
+ els.confirmLogout.textContent = "Yes, Log out";
+}
+
+async function handleLogout() {
+ try {
+ els.confirmLogout.disabled = true;
+ els.confirmLogout.textContent = "Logging out...";
+
+ await apiRequest("/auth/logout", {
+ method: "POST"
+ });
+ } catch (error) {
+ console.warn("Logout failed:", error.message);
+ } finally {
+ localStorage.clear();
+ sessionStorage.clear();
+ window.location.href = "../../index.html";
+ }
+}
+
+async function loadAdminProfile() {
+ try {
+ let profile;
+ try {
+ profile = await apiRequest("/users/me");
+ } catch {
+ profile = await apiRequest("/user/me");
+ }
+
+ els.adminName.textContent =
+ profile.fullName ||
+ profile.name ||
+ "Admin User";
+
+ els.adminRole.textContent =
+ profile.role
+ ? profile.role.charAt(0).toUpperCase() + profile.role.slice(1)
+ : "Admin";
+
+ if (profile.profileImage) {
+ els.adminAvatar.src = profile.profileImage;
+ }
+ } catch (error) {
+ console.error("Failed to load admin profile:", error.message);
+ window.location.href = "../profile/admin-profile.html";
+ }
+}
+
+async function loadDashboardData() {
+ try {
+ const [usersPayload, eventsPayload] = await Promise.all([
+ apiRequest("/user").catch(() => apiRequest("/users")),
+ apiRequest("/events")
+ ]);
+
+ const users = normalizeUsers(usersPayload);
+ const events = normalizeEvents(eventsPayload);
+
+ const organizers = users.filter(
+ (user) => String(user.role || "").toLowerCase() === "organizer"
+ );
+
+ const pendingOrganizers = organizers.filter((user) => {
+ const status = String(user.status || "").toLowerCase();
+ const approvalStatus = String(user.approvalStatus || "").toLowerCase();
+ return status === "pending" || approvalStatus === "pending";
+ });
+
+ const activeUsers = users.filter((user) => user.isActive !== false);
+
+ setStatValue(els.organizationCount, organizers.length);
+ setStatValue(els.pendingCount, pendingOrganizers.length);
+ setStatValue(els.eventsCount, events.length);
+ setStatValue(els.activeUsersCount, activeUsers.length);
+
+ const recentActivity = buildRecentActivity(users, events);
+ renderRecentActivity(recentActivity);
+ } catch (error) {
+ console.error("Failed to load dashboard data:", error.message);
+ els.activityTable.innerHTML = `
+
+ Failed to load dashboard data.
+
+ `;
+ }
+}
+
+function bindUI() {
+ els.goVerificationQueue.addEventListener("click", () => {
+ window.location.href = "../verification/verification-queue.html";
+ });
+
+ els.viewOrganizations.addEventListener("click", () => {
+ window.location.href = "../organizations/organization-directory.html";
+ });
+
+ els.viewEvents.addEventListener("click", () => {
+ window.location.href = "../events/events-oversight.html";
+ });
+
+ els.searchInput.addEventListener("input", applyActivitySearch);
+
+ els.logoutBtn.addEventListener("click", openLogoutModal);
+ els.closeLogoutModal.addEventListener("click", closeLogoutModal);
+ els.cancelLogout.addEventListener("click", closeLogoutModal);
+ els.confirmLogout.addEventListener("click", handleLogout);
+
+ els.logoutModal.addEventListener("click", (event) => {
+ if (event.target === els.logoutModal) {
+ closeLogoutModal();
+ }
+ });
+
+ document.addEventListener("keydown", (event) => {
+ if (event.key === "Escape" && !els.logoutModal.classList.contains("hidden")) {
+ closeLogoutModal();
+ }
+ });
+}
+
+document.addEventListener("DOMContentLoaded", async () => {
+ bindUI();
+ await loadAdminProfile();
+ await loadDashboardData();
+});
\ No newline at end of file
diff --git a/Admin/events/event-details.css b/Admin/events/event-details.css
new file mode 100644
index 0000000..0abe53c
--- /dev/null
+++ b/Admin/events/event-details.css
@@ -0,0 +1,186 @@
+ * {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #edf2f6;
+ color: #172033;
+}
+
+.overlay {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1.5rem;
+}
+
+.modal-card {
+ width: 100%;
+ max-width: 42rem;
+ background: #ffffff;
+ border-radius: 1rem;
+ padding: 1.8rem;
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.12);
+}
+
+.modal-header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 1rem;
+ margin-bottom: 1.5rem;
+}
+
+.modal-header h1 {
+ font-size: 1.6rem;
+ font-weight: 700;
+ color: #172033;
+ line-height: 1.3;
+}
+
+.status-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 6rem;
+ padding: 0.45rem 0.85rem;
+ border-radius: 0.55rem;
+ color: #ffffff;
+ font-size: 0.72rem;
+ font-weight: 700;
+ text-transform: capitalize;
+}
+
+.status-badge.published {
+ background: #16a34a;
+}
+
+.status-badge.cancelled {
+ background: #dc2626;
+}
+
+.status-badge.draft {
+ background: #6b7280;
+}
+
+.details-grid {
+ display: grid;
+ grid-template-columns: 8rem 1fr;
+ gap: 0.9rem 1rem;
+ margin-bottom: 1.6rem;
+}
+
+.label {
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: #223f6b;
+}
+
+.value {
+ font-size: 0.9rem;
+ color: #374151;
+ word-break: break-word;
+}
+
+.description-section h2 {
+ font-size: 1rem;
+ font-weight: 700;
+ margin-bottom: 0.7rem;
+ color: #172033;
+}
+
+.description-box {
+ background: #f8fafc;
+ border: 0.0625rem solid #e5e7eb;
+ border-radius: 0.75rem;
+ padding: 1rem;
+ font-size: 0.9rem;
+ line-height: 1.7;
+ color: #374151;
+}
+
+.feedback {
+ min-height: 1.2rem;
+ margin-top: 1rem;
+ text-align: center;
+ font-size: 0.85rem;
+}
+
+.feedback.success {
+ color: #16a34a;
+}
+
+.feedback.error {
+ color: #dc2626;
+}
+
+.action-row {
+ margin-top: 1.3rem;
+ display: flex;
+ justify-content: flex-end;
+ gap: 0.8rem;
+}
+
+.action-btn {
+ border: none;
+ border-radius: 0.55rem;
+ padding: 0.85rem 1.2rem;
+ font-size: 0.9rem;
+ cursor: pointer;
+}
+
+.close-btn {
+ background: #e5e7eb;
+ color: #172033;
+}
+
+.flag-btn {
+ background: #dc2626;
+ color: #ffffff;
+}
+
+.flag-btn:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+}
+
+.social-link {
+ color: #1d7de2;
+ text-decoration: none;
+}
+
+.social-link:hover {
+ text-decoration: underline;
+}
+
+@media (max-width: 36rem) {
+ .modal-card {
+ padding: 1.2rem;
+ }
+
+ .modal-header {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .details-grid {
+ grid-template-columns: 1fr;
+ gap: 0.4rem;
+ }
+
+ .label {
+ margin-top: 0.4rem;
+ }
+
+ .action-row {
+ flex-direction: column;
+ }
+
+ .action-btn {
+ width: 100%;
+ }
+}
\ No newline at end of file
diff --git a/Admin/events/event-details.html b/Admin/events/event-details.html
new file mode 100644
index 0000000..fd70c1d
--- /dev/null
+++ b/Admin/events/event-details.html
@@ -0,0 +1,59 @@
+
+
+
+
+
+ Event Details
+
+
+
+
+
+
+
+
+
+
+
+
+
Org. Name:
+
—
+
+
Social Links:
+
—
+
+
Email:
+
—
+
+
Phone No:
+
—
+
+
Date / Time:
+
—
+
+
Slots Filled
+
—
+
+
+
+
Description
+
+ Loading event description...
+
+
+
+
+
+
+ Close
+ Flag Event
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Admin/events/event-details.js b/Admin/events/event-details.js
new file mode 100644
index 0000000..097cbd9
--- /dev/null
+++ b/Admin/events/event-details.js
@@ -0,0 +1,257 @@
+const API_BASE_URL = "https://aidloop-backend.onrender.com/api";
+
+const els = {
+ eventTitle: document.getElementById("eventTitle"),
+ statusBadge: document.getElementById("statusBadge"),
+ orgName: document.getElementById("orgName"),
+ socialLinks: document.getElementById("socialLinks"),
+ email: document.getElementById("email"),
+ phoneNumber: document.getElementById("phoneNumber"),
+ dateTime: document.getElementById("dateTime"),
+ slotsFilled: document.getElementById("slotsFilled"),
+ description: document.getElementById("description"),
+ feedback: document.getElementById("feedback"),
+ closeBtn: document.getElementById("closeBtn"),
+ flagBtn: document.getElementById("flagBtn")
+};
+
+const eventId = new URLSearchParams(window.location.search).get("id");
+let currentEvent = null;
+
+async function apiRequest(endpoint, options = {}) {
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ ...(options.headers || {})
+ },
+ ...options
+ });
+
+ const contentType = response.headers.get("content-type") || "";
+ const data = contentType.includes("application/json")
+ ? await response.json()
+ : await response.text();
+
+ if (!response.ok) {
+ throw new Error(
+ (data && data.message) ||
+ (data && data.error) ||
+ "Request failed"
+ );
+ }
+
+ return data;
+}
+
+function normalizeEvents(eventsPayload) {
+ if (Array.isArray(eventsPayload)) return eventsPayload;
+ if (Array.isArray(eventsPayload?.events)) return eventsPayload.events;
+ if (Array.isArray(eventsPayload?.data)) return eventsPayload.data;
+ return [];
+}
+
+function formatDate(dateValue) {
+ if (!dateValue) return "—";
+ const date = new Date(dateValue);
+ if (Number.isNaN(date.getTime())) return dateValue;
+ return date.toLocaleDateString("en-GB", {
+ day: "2-digit",
+ month: "long",
+ year: "numeric"
+ });
+}
+
+function formatTimeValue(value) {
+ if (!value) return "";
+ if (String(value).includes("AM") || String(value).includes("PM")) return value;
+
+ const [hours, minutes] = String(value).split(":");
+ if (!hours || !minutes) return value;
+
+ const hourNum = Number(hours);
+ const suffix = hourNum >= 12 ? "PM" : "AM";
+ const normalizedHour = hourNum % 12 || 12;
+ return `${normalizedHour}:${minutes} ${suffix}`;
+}
+
+function getStatusValue(event) {
+ const status = String(event.status || "").toLowerCase();
+
+ if (status.includes("cancel")) return "cancelled";
+ if (status.includes("draft")) return "draft";
+ return "published";
+}
+
+function setStatusBadge(status) {
+ const normalized = getStatusValue({ status });
+ els.statusBadge.textContent =
+ normalized.charAt(0).toUpperCase() + normalized.slice(1);
+
+ els.statusBadge.className = "status-badge";
+ els.statusBadge.classList.add(normalized);
+}
+
+function getOrganizerName(event) {
+ return (
+ event.organizer?.fullName ||
+ event.organizer?.name ||
+ event.organizerName ||
+ "—"
+ );
+}
+
+function getOrganizerEmail(event) {
+ return (
+ event.organizer?.email ||
+ event.contactEmail ||
+ event.email ||
+ "—"
+ );
+}
+
+function getOrganizerPhone(event) {
+ return (
+ event.organizer?.phoneNumber ||
+ event.organizer?.phone ||
+ event.phoneNumber ||
+ event.phone ||
+ "—"
+ );
+}
+
+function getSocialLink(event) {
+ return (
+ event.organizer?.website ||
+ event.organizer?.socialLink ||
+ event.organizer?.socialLinks?.[0] ||
+ event.website ||
+ event.socialLink ||
+ event.socialLinks?.[0] ||
+ ""
+ );
+}
+
+function renderSocialLinks(link) {
+ if (!link) {
+ els.socialLinks.textContent = "—";
+ return;
+ }
+
+ els.socialLinks.innerHTML = `
+
+ ${link}
+
+ `;
+}
+
+function getSlotsFilled(event) {
+ const filled =
+ event.filledSlots ??
+ event.registrationsCount ??
+ event.registeredCount ??
+ event.attendeesCount ??
+ 0;
+
+ const total = event.volunteerSlots ?? event.slots ?? 0;
+ return `${filled} / ${total}`;
+}
+
+function populateEvent(event) {
+ els.eventTitle.textContent = event.name || event.title || "Untitled Event";
+ setStatusBadge(event.status);
+
+ els.orgName.textContent = getOrganizerName(event);
+ els.email.textContent = getOrganizerEmail(event);
+ els.phoneNumber.textContent = getOrganizerPhone(event);
+
+ renderSocialLinks(getSocialLink(event));
+
+ const formattedDate = formatDate(event.date);
+ const startTime = formatTimeValue(event.startTime);
+ const endTime = formatTimeValue(event.endTime);
+
+ els.dateTime.textContent =
+ startTime && endTime
+ ? `${formattedDate} • ${startTime} - ${endTime}`
+ : startTime
+ ? `${formattedDate} • ${startTime}`
+ : formattedDate;
+
+ els.slotsFilled.textContent = getSlotsFilled(event);
+ els.description.textContent =
+ event.description || "No event description available.";
+}
+
+function setFeedback(message, type = "") {
+ els.feedback.textContent = message;
+ els.feedback.className = "feedback";
+ if (type) {
+ els.feedback.classList.add(type);
+ }
+}
+
+async function loadEventDetails() {
+ if (!eventId) {
+ setFeedback("No event ID provided.", "error");
+ els.flagBtn.disabled = true;
+ return;
+ }
+
+ try {
+ const payload = await apiRequest("/events");
+ const events = normalizeEvents(payload);
+
+ currentEvent = events.find(
+ (event) => String(event._id || event.id) === String(eventId)
+ );
+
+ if (!currentEvent) {
+ throw new Error("Event not found");
+ }
+
+ populateEvent(currentEvent);
+ } catch (error) {
+ els.eventTitle.textContent = "Unable to load event";
+ els.description.textContent = "Failed to fetch event details.";
+ setFeedback(error.message || "Failed to load event details.", "error");
+ els.flagBtn.disabled = true;
+ }
+}
+
+async function flagEvent() {
+ if (!eventId) return;
+
+ try {
+ els.flagBtn.disabled = true;
+ els.flagBtn.textContent = "Flagging...";
+
+ await apiRequest(`/events/${eventId}/cancel`, {
+ method: "PATCH",
+ body: JSON.stringify({
+ reason: "Flagged by admin"
+ })
+ });
+
+ setFeedback("Event flagged and cancelled successfully.", "success");
+ setStatusBadge("cancelled");
+ } catch (error) {
+ setFeedback(error.message || "Failed to flag event.", "error");
+ els.flagBtn.disabled = false;
+ els.flagBtn.textContent = "Flag Event";
+ }
+}
+
+function closeModal() {
+ if (window.history.length > 1) {
+ window.history.back();
+ return;
+ }
+
+ window.location.href = "events-oversight.html";
+}
+
+els.closeBtn.addEventListener("click", closeModal);
+els.flagBtn.addEventListener("click", flagEvent);
+
+document.addEventListener("DOMContentLoaded", loadEventDetails);
\ No newline at end of file
diff --git a/Admin/events/events-oversight.css b/Admin/events/events-oversight.css
new file mode 100644
index 0000000..b2b49a5
--- /dev/null
+++ b/Admin/events/events-oversight.css
@@ -0,0 +1,445 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #edf2f6;
+ color: #172033;
+}
+
+.dashboard {
+ display: flex;
+ min-height: 100vh;
+}
+
+.sidebar {
+ width: 15rem;
+ background: #223f6b;
+ color: #ffffff;
+ padding: 1.8rem 0.9rem;
+}
+
+.sidebar-logo {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.35rem;
+ margin-bottom: 2.4rem;
+}
+
+.sidebar-logo img {
+ width: 3.1rem;
+ height: 3.1rem;
+ object-fit: contain;
+}
+
+.sidebar-menu {
+ list-style: none;
+}
+
+.sidebar-menu li {
+ margin-bottom: 0.8rem;
+}
+
+.sidebar-menu li a,
+.logout-btn {
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+ text-decoration: none;
+ color: #ffffff;
+ padding: 0.9rem 0.85rem;
+ border-radius: 0.45rem;
+ font-size: 0.95rem;
+ transition: background 0.2s ease;
+ width: 100%;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+}
+
+.sidebar-menu li a i,
+.logout-btn i {
+ width: 1rem;
+ text-align: center;
+}
+
+.sidebar-menu li.active a,
+.sidebar-menu li a:hover {
+ background: #1d7de2;
+}
+
+.logout-btn:hover {
+ background: #ef2f2f;
+}
+
+.main-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.topbar {
+ height: 5.8rem;
+ background: #ffffff;
+ border-bottom: 0.0625rem solid #e6e8eb;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 1.8rem;
+}
+
+.search-box {
+ width: 21rem;
+ height: 2.9rem;
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ border: 0.0625rem solid #d9dde3;
+ border-radius: 0.35rem;
+ padding: 0 0.9rem;
+ background: #ffffff;
+}
+
+.search-box i {
+ color: #6b7280;
+ font-size: 0.95rem;
+}
+
+.search-box input {
+ width: 100%;
+ border: none;
+ outline: none;
+ background: transparent;
+ font-size: 0.95rem;
+ color: #374151;
+}
+
+.topbar-right {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.notification {
+ position: relative;
+ color: #111827;
+ font-size: 1.2rem;
+}
+
+.notification-dot {
+ position: absolute;
+ top: 0.08rem;
+ right: -0.12rem;
+ width: 0.45rem;
+ height: 0.45rem;
+ background: #ef2f2f;
+ border-radius: 50%;
+}
+
+.admin-profile {
+ display: flex;
+ align-items: center;
+ gap: 0.7rem;
+}
+
+.user-avatar {
+ width: 2.45rem;
+ height: 2.45rem;
+ border-radius: 50%;
+ object-fit: cover;
+}
+
+.admin-text h4 {
+ font-size: 0.82rem;
+ font-weight: 600;
+ color: #172033;
+ line-height: 1.2;
+}
+
+.admin-text p {
+ font-size: 0.72rem;
+ color: #6b7280;
+ line-height: 1.2;
+}
+
+.admin-chevron {
+ color: #6b7280;
+ font-size: 0.7rem;
+}
+
+.page-content {
+ padding: 4rem 1.4rem 2rem;
+}
+
+.page-header {
+ margin-bottom: 2rem;
+}
+
+.page-header h1 {
+ font-size: 2.1rem;
+ font-weight: 700;
+ margin-bottom: 0.3rem;
+}
+
+.page-header p {
+ font-size: 0.78rem;
+ color: #6b7280;
+}
+
+.filter-buttons {
+ display: flex;
+ gap: 0.8rem;
+ margin-bottom: 1.4rem;
+ flex-wrap: wrap;
+}
+
+.filter-btn {
+ border: 0.0625rem solid #d9dde3;
+ background: #ffffff;
+ color: #374151;
+ padding: 0.75rem 1rem;
+ border-radius: 0.4rem;
+ font-size: 0.8rem;
+ cursor: pointer;
+}
+
+.filter-btn.active {
+ background: #1d7de2;
+ color: #ffffff;
+ border-color: #1d7de2;
+}
+
+.table-wrapper {
+ background: #ffffff;
+ overflow: hidden;
+ border-radius: 0.6rem;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+thead {
+ background: #f4f6f8;
+}
+
+th,
+td {
+ text-align: left;
+ padding: 1rem 1.4rem;
+}
+
+th {
+ font-size: 0.62rem;
+ letter-spacing: 0.08em;
+ color: #7b8492;
+ font-weight: 700;
+}
+
+td {
+ font-size: 0.72rem;
+ color: #172033;
+ border-top: 0.0625rem solid #eef1f3;
+}
+
+.status-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 5.2rem;
+ padding: 0.35rem 0.7rem;
+ border-radius: 0.45rem;
+ color: #ffffff;
+ font-size: 0.58rem;
+ font-weight: 700;
+}
+
+.status-badge.published {
+ background: #16a34a;
+}
+
+.status-badge.cancelled {
+ background: #dc2626;
+}
+
+.status-badge.draft {
+ background: #6b7280;
+}
+
+.action-link {
+ color: #1d7de2;
+ text-decoration: none;
+ font-weight: 600;
+}
+
+.action-link:hover {
+ text-decoration: underline;
+}
+
+.empty-state {
+ text-align: center;
+ padding: 3rem 1rem;
+}
+
+.empty-icon {
+ width: 4rem;
+ height: 4rem;
+ margin: 0 auto 1rem;
+ border-radius: 50%;
+ background: #e8f0fb;
+ color: #1d7de2;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.4rem;
+}
+
+.empty-state h2 {
+ font-size: 1.1rem;
+ margin-bottom: 0.4rem;
+}
+
+.empty-state p {
+ color: #6b7280;
+ font-size: 0.85rem;
+}
+
+/* Logout Modal */
+.hidden {
+ display: none !important;
+}
+
+.modal-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(15, 23, 42, 0.45);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1rem;
+ z-index: 999;
+}
+
+.modal-card {
+ width: 100%;
+ max-width: 25rem;
+ background: #ffffff;
+ border-radius: 1rem;
+ padding: 1.5rem;
+ position: relative;
+ text-align: center;
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.15);
+}
+
+.modal-close-btn {
+ position: absolute;
+ top: 0.7rem;
+ right: 0.9rem;
+ border: none;
+ background: transparent;
+ font-size: 1.5rem;
+ color: #6b7280;
+ cursor: pointer;
+}
+
+.modal-icon-wrap {
+ width: 3.5rem;
+ height: 3.5rem;
+ margin: 0 auto 1rem;
+ border-radius: 50%;
+ background: #fee2e2;
+ color: #dc2626;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.25rem;
+}
+
+.modal-card h3 {
+ font-size: 1.2rem;
+ margin-bottom: 0.5rem;
+ color: #172033;
+}
+
+.modal-card p {
+ font-size: 0.92rem;
+ color: #6b7280;
+ margin-bottom: 1.3rem;
+}
+
+.modal-actions {
+ display: flex;
+ gap: 0.8rem;
+ justify-content: center;
+}
+
+.modal-btn {
+ border: none;
+ border-radius: 0.55rem;
+ padding: 0.8rem 1rem;
+ font-size: 0.9rem;
+ cursor: pointer;
+ min-width: 8rem;
+}
+
+.modal-btn.secondary {
+ background: #e5e7eb;
+ color: #172033;
+}
+
+.modal-btn.danger {
+ background: #dc2626;
+ color: #ffffff;
+}
+
+@media (max-width: 75rem) {
+ .table-wrapper {
+ overflow-x: auto;
+ }
+
+ table {
+ min-width: 46rem;
+ }
+}
+
+@media (max-width: 62rem) {
+ .sidebar {
+ display: none;
+ }
+
+ .topbar {
+ padding: 0 1rem;
+ }
+
+ .search-box {
+ width: 16rem;
+ }
+
+ .page-content {
+ padding: 2rem 1rem;
+ }
+
+ .admin-text,
+ .admin-chevron {
+ display: none;
+ }
+}
+
+@media (max-width: 36rem) {
+ .search-box {
+ width: 12rem;
+ }
+
+ .modal-actions {
+ flex-direction: column;
+ }
+
+ .modal-btn {
+ width: 100%;
+ }
+}
\ No newline at end of file
diff --git a/Admin/events/events-oversight.html b/Admin/events/events-oversight.html
new file mode 100644
index 0000000..ca5cbc2
--- /dev/null
+++ b/Admin/events/events-oversight.html
@@ -0,0 +1,157 @@
+
+
+
+
+
+ AIDLoop - Events Oversight
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Status: All
+
+ Published
+ Cancelled
+
+
+
+
+
+
+ EVENT NAME
+ CONTACT EMAIL
+ LOCATION
+ STATUS
+ ACTIONS
+
+
+
+
+
+ Loading events...
+
+
+
+
+
+
+
+
+
No Events Found
+
Events created by organizations will appear here.
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
Log out?
+
You are about to end your current admin session.
+
+
+ Cancel
+ Yes, Log out
+
+
+
+
+
+
+
+
+
diff --git a/Admin/events/events-oversight.js b/Admin/events/events-oversight.js
new file mode 100644
index 0000000..c181244
--- /dev/null
+++ b/Admin/events/events-oversight.js
@@ -0,0 +1,258 @@
+const API_BASE_URL = "https://aidloop-backend.onrender.com/api";
+
+const els = {
+ adminName: document.getElementById("adminName"),
+ adminRole: document.getElementById("adminRole"),
+ adminAvatar: document.getElementById("adminAvatar"),
+ eventsTable: document.getElementById("eventsTable"),
+ eventsTableWrap: document.getElementById("eventsTableWrap"),
+ emptyState: document.getElementById("emptyState"),
+ searchInput: document.getElementById("searchInput"),
+ filterButtons: document.querySelectorAll(".filter-btn"),
+ logoutBtn: document.getElementById("logoutBtn"),
+ logoutModal: document.getElementById("logoutModal"),
+ closeLogoutModal: document.getElementById("closeLogoutModal"),
+ cancelLogout: document.getElementById("cancelLogout"),
+ confirmLogout: document.getElementById("confirmLogout")
+};
+
+let eventsCache = [];
+let currentFilter = "all";
+
+async function apiRequest(endpoint, options = {}) {
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ ...(options.headers || {})
+ },
+ ...options
+ });
+
+ const contentType = response.headers.get("content-type") || "";
+ const data = contentType.includes("application/json")
+ ? await response.json()
+ : await response.text();
+
+ if (!response.ok) {
+ throw new Error(
+ (data && data.message) ||
+ (data && data.error) ||
+ "Request failed"
+ );
+ }
+
+ return data;
+}
+
+function normalizeEvents(eventsPayload) {
+ if (Array.isArray(eventsPayload)) return eventsPayload;
+ if (Array.isArray(eventsPayload?.events)) return eventsPayload.events;
+ if (Array.isArray(eventsPayload?.data)) return eventsPayload.data;
+ return [];
+}
+
+function formatLocation(event) {
+ if (typeof event.location === "string" && event.location.trim()) {
+ return event.location;
+ }
+
+ if (event.location && typeof event.location === "object") {
+ return (
+ [event.location.venue, event.location.city || event.location.state]
+ .filter(Boolean)
+ .join(", ") || "—"
+ );
+ }
+
+ return event.city || event.state || "—";
+}
+
+function getStatusValue(event) {
+ const status = String(event.status || "").toLowerCase();
+
+ if (status.includes("cancel")) return "cancelled";
+ if (status.includes("draft")) return "draft";
+ return "published";
+}
+
+function getContactEmail(event) {
+ return (
+ event.organizer?.email ||
+ event.contactEmail ||
+ event.email ||
+ "—"
+ );
+}
+
+function getEventTitle(event) {
+ return event.name || event.title || "Untitled Event";
+}
+
+function getEventId(event) {
+ return event._id || event.id || "";
+}
+
+function renderEvents() {
+ const query = els.searchInput.value.trim().toLowerCase();
+
+ let filtered = [...eventsCache];
+
+ if (currentFilter !== "all") {
+ filtered = filtered.filter((event) => getStatusValue(event) === currentFilter);
+ }
+
+ if (query) {
+ filtered = filtered.filter((event) => {
+ const searchableText = `
+ ${getEventTitle(event)}
+ ${getContactEmail(event)}
+ ${formatLocation(event)}
+ ${getStatusValue(event)}
+ `.toLowerCase();
+
+ return searchableText.includes(query);
+ });
+ }
+
+ if (!filtered.length) {
+ els.eventsTableWrap.style.display = "none";
+ els.emptyState.style.display = "block";
+ return;
+ }
+
+ els.eventsTableWrap.style.display = "table";
+ els.emptyState.style.display = "none";
+
+ els.eventsTable.innerHTML = filtered.map((event) => {
+ const status = getStatusValue(event);
+
+ return `
+
+ ${getEventTitle(event)}
+ ${getContactEmail(event)}
+ ${formatLocation(event)}
+
+
+ ${status.charAt(0).toUpperCase() + status.slice(1)}
+
+
+
+
+ View Details
+
+
+
+ `;
+ }).join("");
+}
+
+function bindFilters() {
+ els.filterButtons.forEach((button) => {
+ button.addEventListener("click", () => {
+ els.filterButtons.forEach((btn) => btn.classList.remove("active"));
+ button.classList.add("active");
+ currentFilter = button.dataset.filter;
+ renderEvents();
+ });
+ });
+}
+
+function openLogoutModal() {
+ els.logoutModal.classList.remove("hidden");
+}
+
+function closeLogoutModal() {
+ els.logoutModal.classList.add("hidden");
+ els.confirmLogout.disabled = false;
+ els.confirmLogout.textContent = "Yes, Log out";
+}
+
+async function handleLogout() {
+ try {
+ els.confirmLogout.disabled = true;
+ els.confirmLogout.textContent = "Logging out...";
+
+ await apiRequest("/auth/logout", {
+ method: "POST"
+ });
+ } catch (error) {
+ console.warn("Logout failed:", error.message);
+ } finally {
+ localStorage.clear();
+ sessionStorage.clear();
+ window.location.href = "../../index.html";
+ }
+}
+
+async function loadAdminProfile() {
+ try {
+ let profile;
+ try {
+ profile = await apiRequest("/users/me");
+ } catch {
+ profile = await apiRequest("/user/me");
+ }
+
+ els.adminName.textContent =
+ profile.fullName ||
+ profile.name ||
+ "Admin User";
+
+ els.adminRole.textContent =
+ profile.role
+ ? profile.role.charAt(0).toUpperCase() + profile.role.slice(1)
+ : "Admin";
+
+ if (profile.profileImage) {
+ els.adminAvatar.src = profile.profileImage;
+ }
+ } catch (error) {
+ console.error("Failed to load admin profile:", error.message);
+ window.location.href = "../profile/admin-profile.html";
+ }
+}
+
+async function loadEvents() {
+ try {
+ const payload = await apiRequest("/events");
+ eventsCache = normalizeEvents(payload);
+ renderEvents();
+ } catch (error) {
+ console.error("Failed to load events:", error.message);
+ els.eventsTable.innerHTML = `
+
+ Failed to load events.
+
+ `;
+ }
+}
+
+function bindUI() {
+ els.searchInput.addEventListener("input", renderEvents);
+
+ bindFilters();
+
+ els.logoutBtn.addEventListener("click", openLogoutModal);
+ els.closeLogoutModal.addEventListener("click", closeLogoutModal);
+ els.cancelLogout.addEventListener("click", closeLogoutModal);
+ els.confirmLogout.addEventListener("click", handleLogout);
+
+ els.logoutModal.addEventListener("click", (event) => {
+ if (event.target === els.logoutModal) {
+ closeLogoutModal();
+ }
+ });
+
+ document.addEventListener("keydown", (event) => {
+ if (event.key === "Escape" && !els.logoutModal.classList.contains("hidden")) {
+ closeLogoutModal();
+ }
+ });
+}
+
+document.addEventListener("DOMContentLoaded", async () => {
+ bindUI();
+ await loadAdminProfile();
+ await loadEvents();
+});
\ No newline at end of file
diff --git a/Admin/flags/flag-details.css b/Admin/flags/flag-details.css
new file mode 100644
index 0000000..f7e2a6b
--- /dev/null
+++ b/Admin/flags/flag-details.css
@@ -0,0 +1,198 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #edf2f6;
+ color: #172033;
+}
+
+.overlay {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1.5rem;
+}
+
+.modal-card {
+ width: 100%;
+ max-width: 40rem;
+ background: #ffffff;
+ border-radius: 1rem;
+ padding: 1.8rem;
+ position: relative;
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.12);
+}
+
+.close-btn {
+ position: absolute;
+ top: 0.8rem;
+ right: 1rem;
+ border: none;
+ background: transparent;
+ color: #6b7280;
+ font-size: 1.5rem;
+ cursor: pointer;
+}
+
+.close-btn:hover {
+ color: #172033;
+}
+
+.modal-header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 1rem;
+ margin-bottom: 1.5rem;
+}
+
+.modal-header h1 {
+ font-size: 1.6rem;
+ font-weight: 700;
+ color: #172033;
+ line-height: 1.3;
+}
+
+.severity-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 5.5rem;
+ padding: 0.45rem 0.8rem;
+ border-radius: 0.55rem;
+ color: #ffffff;
+ font-size: 0.72rem;
+ font-weight: 700;
+ text-transform: capitalize;
+}
+
+.severity-badge.low {
+ background: #16a34a;
+}
+
+.severity-badge.medium {
+ background: #f59e0b;
+}
+
+.severity-badge.high {
+ background: #dc2626;
+}
+
+.details-grid {
+ display: grid;
+ grid-template-columns: 9rem 1fr;
+ gap: 0.9rem 1rem;
+ margin-bottom: 1.5rem;
+}
+
+.label {
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: #223f6b;
+}
+
+.value {
+ font-size: 0.9rem;
+ color: #374151;
+ word-break: break-word;
+}
+
+.description-section h2 {
+ font-size: 1rem;
+ font-weight: 700;
+ margin-bottom: 0.7rem;
+ color: #172033;
+}
+
+.description-box {
+ background: #f8fafc;
+ border: 0.0625rem solid #e5e7eb;
+ border-radius: 0.75rem;
+ padding: 1rem;
+ font-size: 0.9rem;
+ line-height: 1.7;
+ color: #374151;
+}
+
+.feedback {
+ min-height: 1.2rem;
+ margin-top: 1rem;
+ text-align: center;
+ font-size: 0.85rem;
+}
+
+.feedback.success {
+ color: #16a34a;
+}
+
+.feedback.error {
+ color: #dc2626;
+}
+
+.action-row {
+ margin-top: 1.3rem;
+ display: flex;
+ justify-content: flex-end;
+}
+
+.contact-btn {
+ border: none;
+ border-radius: 0.55rem;
+ padding: 0.85rem 1.2rem;
+ font-size: 0.9rem;
+ cursor: pointer;
+ background: #223f6b;
+ color: #ffffff;
+}
+
+.contact-btn:hover {
+ opacity: 0.94;
+}
+
+.contact-btn:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+}
+
+@media (max-width: 36rem) {
+ .modal-card {
+ padding: 1.2rem;
+ }
+
+ .modal-header {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .details-grid {
+ grid-template-columns: 1fr;
+ gap: 0.4rem;
+ }
+
+ .label {
+ margin-top: 0.4rem;
+ }
+
+ .action-row {
+ justify-content: stretch;
+ }
+
+ .contact-btn {
+ width: 100%;
+ }
+}
+
+
+
+
+
+
+
+
+
+
diff --git a/Admin/flags/flag-details.html b/Admin/flags/flag-details.html
new file mode 100644
index 0000000..575412e
--- /dev/null
+++ b/Admin/flags/flag-details.html
@@ -0,0 +1,51 @@
+
+
+
+
+
+ Flag Details
+
+
+
+
+
+
+
+
+
+
×
+
+
+
+
+
Org. Name:
+
Loading...
+
+
Flag Reason:
+
—
+
+
Last Event Cancelled
+
—
+
+
+
+
Description
+
+ Loading organizer description...
+
+
+
+
+
+
+ Contact Organizer
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Admin/flags/flag-details.js b/Admin/flags/flag-details.js
new file mode 100644
index 0000000..73f8d65
--- /dev/null
+++ b/Admin/flags/flag-details.js
@@ -0,0 +1,216 @@
+const API_BASE_URL = "https://aidloop-backend.onrender.com/api";
+
+const els = {
+ closeBtn: document.getElementById("closeBtn"),
+ orgTitle: document.getElementById("orgTitle"),
+ severityBadge: document.getElementById("severityBadge"),
+ orgName: document.getElementById("orgName"),
+ flagReason: document.getElementById("flagReason"),
+ lastEventCancelled: document.getElementById("lastEventCancelled"),
+ description: document.getElementById("description"),
+ feedback: document.getElementById("feedback"),
+ contactBtn: document.getElementById("contactBtn")
+};
+
+const organizerId = new URLSearchParams(window.location.search).get("id");
+let currentOrganizer = null;
+let currentCancelledEvent = null;
+
+async function apiRequest(endpoint, options = {}) {
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ ...(options.headers || {})
+ },
+ ...options
+ });
+
+ const contentType = response.headers.get("content-type") || "";
+ const data = contentType.includes("application/json")
+ ? await response.json()
+ : await response.text();
+
+ if (!response.ok) {
+ throw new Error(
+ (data && data.message) ||
+ (data && data.error) ||
+ "Request failed"
+ );
+ }
+
+ return data;
+}
+
+function normalizeUsers(payload) {
+ if (Array.isArray(payload)) return payload;
+ if (Array.isArray(payload?.users)) return payload.users;
+ if (Array.isArray(payload?.data)) return payload.data;
+ return [];
+}
+
+function normalizeEvents(payload) {
+ if (Array.isArray(payload)) return payload;
+ if (Array.isArray(payload?.events)) return payload.events;
+ if (Array.isArray(payload?.data)) return payload.data;
+ return [];
+}
+
+function formatDate(dateValue) {
+ if (!dateValue) return "—";
+ const date = new Date(dateValue);
+ if (Number.isNaN(date.getTime())) return dateValue;
+ return date.toLocaleDateString("en-GB", {
+ day: "2-digit",
+ month: "long",
+ year: "numeric"
+ });
+}
+
+function getOrganizerName(user) {
+ return user.fullName || user.name || user.organizationName || "Organization";
+}
+
+function getSeverity(count) {
+ if (count <= 2) return "low";
+ if (count <= 4) return "medium";
+ return "high";
+}
+
+function getSeverityLabel(count) {
+ const severity = getSeverity(count);
+ return severity.charAt(0).toUpperCase() + severity.slice(1);
+}
+
+function setSeverity(count) {
+ const severity = getSeverity(count);
+ els.severityBadge.textContent = getSeverityLabel(count);
+ els.severityBadge.className = "severity-badge";
+ els.severityBadge.classList.add(severity);
+}
+
+function setFeedback(message, type = "") {
+ els.feedback.textContent = message;
+ els.feedback.className = "feedback";
+ if (type) {
+ els.feedback.classList.add(type);
+ }
+}
+
+function populateDetails(organizer, cancelledEvents) {
+ const latestCancelled = [...cancelledEvents].sort(
+ (a, b) =>
+ new Date(b.date || b.updatedAt || b.createdAt || 0) -
+ new Date(a.date || a.updatedAt || a.createdAt || 0)
+ )[0];
+
+ currentOrganizer = organizer;
+ currentCancelledEvent = latestCancelled || null;
+
+ const cancellationsCount = cancelledEvents.length;
+ const reason =
+ latestCancelled?.cancelReason ||
+ latestCancelled?.reason ||
+ "Frequent cancellations";
+
+ els.orgTitle.textContent = getOrganizerName(organizer);
+ els.orgName.textContent = getOrganizerName(organizer);
+ els.flagReason.textContent = reason;
+
+ els.lastEventCancelled.textContent = latestCancelled
+ ? `${latestCancelled.name || latestCancelled.title || "Untitled Event"} • ${formatDate(
+ latestCancelled.date || latestCancelled.updatedAt || latestCancelled.createdAt
+ )}`
+ : "—";
+
+ els.description.textContent =
+ organizer.description ||
+ organizer.bio ||
+ "No organizer description available.";
+
+ setSeverity(cancellationsCount);
+}
+
+async function loadFlagDetails() {
+ if (!organizerId) {
+ setFeedback("No organizer ID provided.", "error");
+ els.contactBtn.disabled = true;
+ return;
+ }
+
+ try {
+ const [usersPayload, eventsPayload] = await Promise.all([
+ apiRequest("/user").catch(() => apiRequest("/users")),
+ apiRequest("/events")
+ ]);
+
+ const users = normalizeUsers(usersPayload);
+ const events = normalizeEvents(eventsPayload);
+
+ const organizer = users.find(
+ (user) => String(user._id || user.id) === String(organizerId)
+ );
+
+ if (!organizer) {
+ throw new Error("Organizer not found");
+ }
+
+ const cancelledEvents = events.filter((event) => {
+ const eventOrganizerId = String(
+ event.organizer?._id ||
+ event.organizer?.id ||
+ event.organizerId ||
+ ""
+ );
+ const status = String(event.status || "").toLowerCase();
+ return (
+ eventOrganizerId === String(organizerId) &&
+ status.includes("cancel")
+ );
+ });
+
+ if (!cancelledEvents.length) {
+ throw new Error("No flagged cancelled events found for this organizer");
+ }
+
+ populateDetails(organizer, cancelledEvents);
+ } catch (error) {
+ els.orgTitle.textContent = "Unable to load flag details";
+ els.description.textContent = "Failed to fetch organizer flag details.";
+ setFeedback(error.message || "Failed to load flag details.", "error");
+ els.contactBtn.disabled = true;
+ }
+}
+
+function contactOrganizer() {
+ if (!currentOrganizer) return;
+
+ const organizerName = getOrganizerName(currentOrganizer);
+ const organizerEmail = currentOrganizer.email || "";
+ const subject = encodeURIComponent(`AidLoop Flag Review - ${organizerName}`);
+ const body = encodeURIComponent(
+ `Hello ${organizerName},\n\nWe are contacting you regarding flagged activity connected to your recent event record on AidLoop.\n\nPlease reply with clarification on the cancelled event and any relevant updates.\n\nThank you.`
+ );
+
+ if (!organizerEmail) {
+ setFeedback("No organizer email available.", "error");
+ return;
+ }
+
+ window.location.href = `mailto:${organizerEmail}?subject=${subject}&body=${body}`;
+ setFeedback("Opening your email client...", "success");
+}
+
+function closeModal() {
+ if (window.history.length > 1) {
+ window.history.back();
+ return;
+ }
+
+ window.location.href = "flags.html";
+}
+
+els.closeBtn.addEventListener("click", closeModal);
+els.contactBtn.addEventListener("click", contactOrganizer);
+
+document.addEventListener("DOMContentLoaded", loadFlagDetails);
\ No newline at end of file
diff --git a/Admin/flags/flags.css b/Admin/flags/flags.css
new file mode 100644
index 0000000..be6623a
--- /dev/null
+++ b/Admin/flags/flags.css
@@ -0,0 +1,451 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #edf2f6;
+ color: #172033;
+}
+
+.dashboard {
+ display: flex;
+ min-height: 100vh;
+}
+
+.sidebar {
+ width: 15rem;
+ background: #223f6b;
+ color: #ffffff;
+ padding: 1.8rem 0.9rem;
+}
+
+.sidebar-logo {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.35rem;
+ margin-bottom: 2.4rem;
+}
+
+.sidebar-logo img {
+ width: 3.1rem;
+ height: 3.1rem;
+ object-fit: contain;
+}
+
+.sidebar-menu {
+ list-style: none;
+}
+
+.sidebar-menu li {
+ margin-bottom: 0.8rem;
+}
+
+.sidebar-menu li a,
+.logout-btn {
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+ text-decoration: none;
+ color: #ffffff;
+ padding: 0.9rem 0.85rem;
+ border-radius: 0.45rem;
+ font-size: 0.95rem;
+ transition: background 0.2s ease;
+ width: 100%;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+}
+
+.sidebar-menu li a i,
+.logout-btn i {
+ width: 1rem;
+ text-align: center;
+}
+
+.sidebar-menu li.active a,
+.sidebar-menu li a:hover {
+ background: #1d7de2;
+}
+
+.logout-btn:hover {
+ background: #ef2f2f;
+}
+
+.main-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.topbar {
+ height: 5.8rem;
+ background: #ffffff;
+ border-bottom: 0.0625rem solid #e6e8eb;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 1.8rem;
+}
+
+.search-box {
+ width: 21rem;
+ height: 2.9rem;
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ border: 0.0625rem solid #d9dde3;
+ border-radius: 0.35rem;
+ padding: 0 0.9rem;
+ background: #ffffff;
+}
+
+.search-box i {
+ color: #6b7280;
+ font-size: 0.95rem;
+}
+
+.search-box input {
+ width: 100%;
+ border: none;
+ outline: none;
+ background: transparent;
+ font-size: 0.95rem;
+ color: #374151;
+}
+
+.topbar-right {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.notification {
+ position: relative;
+ color: #111827;
+ font-size: 1.2rem;
+}
+
+.notification-dot {
+ position: absolute;
+ top: 0.08rem;
+ right: -0.12rem;
+ width: 0.45rem;
+ height: 0.45rem;
+ background: #ef2f2f;
+ border-radius: 50%;
+}
+
+.admin-profile {
+ display: flex;
+ align-items: center;
+ gap: 0.7rem;
+}
+
+.user-avatar {
+ width: 2.45rem;
+ height: 2.45rem;
+ border-radius: 50%;
+ object-fit: cover;
+}
+
+.admin-text h4 {
+ font-size: 0.82rem;
+ font-weight: 600;
+ color: #172033;
+ line-height: 1.2;
+}
+
+.admin-text p {
+ font-size: 0.72rem;
+ color: #6b7280;
+ line-height: 1.2;
+}
+
+.admin-chevron {
+ color: #6b7280;
+ font-size: 0.7rem;
+}
+
+.page-content {
+ padding: 4rem 1.4rem 2rem;
+}
+
+.page-header {
+ margin-bottom: 2rem;
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 1rem;
+}
+
+.page-title-group h1 {
+ font-size: 2.1rem;
+ font-weight: 700;
+ margin-bottom: 0.3rem;
+}
+
+.page-title-group p {
+ font-size: 0.78rem;
+ color: #6b7280;
+}
+
+.filter-buttons {
+ display: flex;
+ gap: 0.8rem;
+ flex-wrap: wrap;
+}
+
+.filter-btn {
+ border: 0.0625rem solid #d9dde3;
+ background: #ffffff;
+ color: #374151;
+ padding: 0.75rem 1rem;
+ border-radius: 0.4rem;
+ font-size: 0.8rem;
+ cursor: pointer;
+}
+
+.filter-btn.active {
+ background: #1d7de2;
+ color: #ffffff;
+ border-color: #1d7de2;
+}
+
+.table-wrapper {
+ background: #ffffff;
+ overflow: hidden;
+ border-radius: 0.6rem;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+thead {
+ background: #f4f6f8;
+}
+
+th,
+td {
+ text-align: left;
+ padding: 1rem 1.4rem;
+}
+
+th {
+ font-size: 0.62rem;
+ letter-spacing: 0.08em;
+ color: #7b8492;
+ font-weight: 700;
+}
+
+td {
+ font-size: 0.72rem;
+ color: #172033;
+ border-top: 0.0625rem solid #eef1f3;
+}
+
+.severity-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 5rem;
+ padding: 0.35rem 0.7rem;
+ border-radius: 0.45rem;
+ color: #ffffff;
+ font-size: 0.58rem;
+ font-weight: 700;
+}
+
+.severity-badge.low {
+ background: #16a34a;
+}
+
+.severity-badge.medium {
+ background: #f59e0b;
+}
+
+.severity-badge.high {
+ background: #dc2626;
+}
+
+.action-link {
+ color: #1d7de2;
+ text-decoration: none;
+ font-weight: 600;
+}
+
+.action-link:hover {
+ text-decoration: underline;
+}
+
+.empty-state {
+ text-align: center;
+ padding: 3rem 1rem;
+}
+
+.empty-icon {
+ width: 4rem;
+ height: 4rem;
+ margin: 0 auto 1rem;
+ border-radius: 50%;
+ background: #e8f0fb;
+ color: #1d7de2;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.4rem;
+}
+
+.empty-state h2 {
+ font-size: 1.1rem;
+ margin-bottom: 0.4rem;
+}
+
+.empty-state p {
+ color: #6b7280;
+ font-size: 0.85rem;
+}
+
+.hidden {
+ display: none !important;
+}
+
+.modal-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(15, 23, 42, 0.45);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1rem;
+ z-index: 999;
+}
+
+.modal-card {
+ width: 100%;
+ max-width: 25rem;
+ background: #ffffff;
+ border-radius: 1rem;
+ padding: 1.5rem;
+ position: relative;
+ text-align: center;
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.15);
+}
+
+.modal-close-btn {
+ position: absolute;
+ top: 0.7rem;
+ right: 0.9rem;
+ border: none;
+ background: transparent;
+ font-size: 1.5rem;
+ color: #6b7280;
+ cursor: pointer;
+}
+
+.modal-icon-wrap {
+ width: 3.5rem;
+ height: 3.5rem;
+ margin: 0 auto 1rem;
+ border-radius: 50%;
+ background: #fee2e2;
+ color: #dc2626;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.25rem;
+}
+
+.modal-card h3 {
+ font-size: 1.2rem;
+ margin-bottom: 0.5rem;
+ color: #172033;
+}
+
+.modal-card p {
+ font-size: 0.92rem;
+ color: #6b7280;
+ margin-bottom: 1.3rem;
+}
+
+.modal-actions {
+ display: flex;
+ gap: 0.8rem;
+ justify-content: center;
+}
+
+.modal-btn {
+ border: none;
+ border-radius: 0.55rem;
+ padding: 0.8rem 1rem;
+ font-size: 0.9rem;
+ cursor: pointer;
+ min-width: 8rem;
+}
+
+.modal-btn.secondary {
+ background: #e5e7eb;
+ color: #172033;
+}
+
+.modal-btn.danger {
+ background: #dc2626;
+ color: #ffffff;
+}
+
+@media (max-width: 75rem) {
+ .table-wrapper {
+ overflow-x: auto;
+ }
+
+ table {
+ min-width: 56rem;
+ }
+}
+
+@media (max-width: 62rem) {
+ .sidebar {
+ display: none;
+ }
+
+ .topbar {
+ padding: 0 1rem;
+ }
+
+ .search-box {
+ width: 16rem;
+ }
+
+ .page-content {
+ padding: 2rem 1rem;
+ }
+
+ .page-header {
+ flex-direction: column;
+ }
+
+ .admin-text,
+ .admin-chevron {
+ display: none;
+ }
+}
+
+@media (max-width: 36rem) {
+ .search-box {
+ width: 12rem;
+ }
+
+ .modal-actions {
+ flex-direction: column;
+ }
+
+ .modal-btn {
+ width: 100%;
+ }
+}
\ No newline at end of file
diff --git a/Admin/flags/flags.html b/Admin/flags/flags.html
new file mode 100644
index 0000000..43b60ad
--- /dev/null
+++ b/Admin/flags/flags.html
@@ -0,0 +1,153 @@
+
+
+
+
+
+ AIDLoop - Flags
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ORGANIZATION NAME
+ NUMBER OF CANCELLATIONS
+ SEVERITY
+ LAST EVENT DATE
+ FLAG REASON
+ ACTIONS
+
+
+
+
+
+ Loading flags...
+
+
+
+
+
+
+
+
+
No flags found
+
Flagged organizers and events will appear here.
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
Log out?
+
You are about to end your current admin session.
+
+
+ Cancel
+ Yes, Log out
+
+
+
+
+
+
+
diff --git a/Admin/flags/flags.js b/Admin/flags/flags.js
new file mode 100644
index 0000000..22b33d5
--- /dev/null
+++ b/Admin/flags/flags.js
@@ -0,0 +1,317 @@
+const API_BASE_URL = "https://aidloop-backend.onrender.com/api";
+
+const els = {
+ adminName: document.getElementById("adminName"),
+ adminRole: document.getElementById("adminRole"),
+ adminAvatar: document.getElementById("adminAvatar"),
+ flagsTable: document.getElementById("flagsTable"),
+ flagsTableWrap: document.querySelector(".table-wrapper table"),
+ emptyState: document.getElementById("emptyState"),
+ searchInput: document.getElementById("searchInput"),
+ filterButtons: document.querySelectorAll(".filter-btn"),
+ logoutBtn: document.getElementById("logoutBtn"),
+ logoutModal: document.getElementById("logoutModal"),
+ closeLogoutModal: document.getElementById("closeLogoutModal"),
+ cancelLogout: document.getElementById("cancelLogout"),
+ confirmLogout: document.getElementById("confirmLogout")
+};
+
+let flagsCache = [];
+let currentFilter = "all";
+
+async function apiRequest(endpoint, options = {}) {
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ ...(options.headers || {})
+ },
+ ...options
+ });
+
+ const contentType = response.headers.get("content-type") || "";
+ const data = contentType.includes("application/json")
+ ? await response.json()
+ : await response.text();
+
+ if (!response.ok) {
+ throw new Error(
+ (data && data.message) ||
+ (data && data.error) ||
+ "Request failed"
+ );
+ }
+
+ return data;
+}
+
+function normalizeUsers(payload) {
+ if (Array.isArray(payload)) return payload;
+ if (Array.isArray(payload?.users)) return payload.users;
+ if (Array.isArray(payload?.data)) return payload.data;
+ return [];
+}
+
+function normalizeEvents(payload) {
+ if (Array.isArray(payload)) return payload;
+ if (Array.isArray(payload?.events)) return payload.events;
+ if (Array.isArray(payload?.data)) return payload.data;
+ return [];
+}
+
+function formatDate(dateValue) {
+ if (!dateValue) return "—";
+ const date = new Date(dateValue);
+ if (Number.isNaN(date.getTime())) return dateValue;
+ return date.toLocaleDateString("en-GB", {
+ day: "2-digit",
+ month: "short",
+ year: "numeric"
+ });
+}
+
+function getOrganizerStatus(user) {
+ const status = String(user.status || "").toLowerCase();
+ const approvalStatus = String(user.approvalStatus || "").toLowerCase();
+ const isVerified = Boolean(user.isVerified);
+
+ if (status === "rejected" || approvalStatus === "rejected") return "rejected";
+ if (
+ status === "verified" ||
+ status === "approved" ||
+ approvalStatus === "verified" ||
+ approvalStatus === "approved" ||
+ isVerified
+ ) {
+ return "verified";
+ }
+ return "pending";
+}
+
+function getSeverity(count) {
+ if (count <= 2) return "low";
+ if (count <= 4) return "medium";
+ return "high";
+}
+
+function getSeverityLabel(count) {
+ const severity = getSeverity(count);
+ return severity.charAt(0).toUpperCase() + severity.slice(1);
+}
+
+function getOrganizerName(user) {
+ return user.fullName || user.name || user.organizationName || "Organization";
+}
+
+function buildFlags(users, events) {
+ const organizers = users.filter(
+ (user) => String(user.role || "").toLowerCase() === "organizer"
+ );
+
+ return organizers
+ .map((organizer) => {
+ const organizerId = String(organizer._id || organizer.id || "");
+
+ const organizerEvents = events.filter((event) => {
+ const eventOrganizerId =
+ String(event.organizer?._id || event.organizer?.id || event.organizerId || "");
+ return eventOrganizerId === organizerId;
+ });
+
+ const cancelledEvents = organizerEvents.filter((event) => {
+ const status = String(event.status || "").toLowerCase();
+ return status.includes("cancel");
+ });
+
+ if (!cancelledEvents.length) return null;
+
+ const latestCancelled = cancelledEvents.sort(
+ (a, b) => new Date(b.date || b.updatedAt || b.createdAt || 0) - new Date(a.date || a.updatedAt || a.createdAt || 0)
+ )[0];
+
+ return {
+ id: organizerId,
+ status: getOrganizerStatus(organizer),
+ name: getOrganizerName(organizer),
+ cancellations: cancelledEvents.length,
+ severity: getSeverity(cancelledEvents.length),
+ severityLabel: getSeverityLabel(cancelledEvents.length),
+ lastEventDate: latestCancelled?.date || latestCancelled?.updatedAt || latestCancelled?.createdAt || "",
+ reason:
+ latestCancelled?.cancelReason ||
+ latestCancelled?.reason ||
+ "Frequent cancellations"
+ };
+ })
+ .filter(Boolean)
+ .sort((a, b) => new Date(b.lastEventDate || 0) - new Date(a.lastEventDate || 0));
+}
+
+function renderFlags() {
+ const query = els.searchInput.value.trim().toLowerCase();
+
+ let filtered = [...flagsCache];
+
+ if (currentFilter !== "all") {
+ filtered = filtered.filter((item) => item.status === currentFilter);
+ }
+
+ if (query) {
+ filtered = filtered.filter((item) => {
+ const searchableText = `
+ ${item.name}
+ ${item.cancellations}
+ ${item.severityLabel}
+ ${formatDate(item.lastEventDate)}
+ ${item.reason}
+ `.toLowerCase();
+
+ return searchableText.includes(query);
+ });
+ }
+
+ if (!filtered.length) {
+ els.flagsTableWrap.style.display = "none";
+ els.emptyState.style.display = "block";
+ return;
+ }
+
+ els.flagsTableWrap.style.display = "table";
+ els.emptyState.style.display = "none";
+
+ els.flagsTable.innerHTML = filtered.map((item) => `
+
+ ${item.name}
+ ${item.cancellations}
+
+
+ ${item.severityLabel}
+
+
+ ${formatDate(item.lastEventDate)}
+ ${item.reason}
+
+
+ Review
+
+
+
+ `).join("");
+}
+
+function bindFilters() {
+ els.filterButtons.forEach((button) => {
+ button.addEventListener("click", () => {
+ els.filterButtons.forEach((btn) => btn.classList.remove("active"));
+ button.classList.add("active");
+ currentFilter = button.dataset.filter;
+ renderFlags();
+ });
+ });
+}
+
+function openLogoutModal() {
+ els.logoutModal.classList.remove("hidden");
+}
+
+function closeLogoutModal() {
+ els.logoutModal.classList.add("hidden");
+ els.confirmLogout.disabled = false;
+ els.confirmLogout.textContent = "Yes, Log out";
+}
+
+async function handleLogout() {
+ try {
+ els.confirmLogout.disabled = true;
+ els.confirmLogout.textContent = "Logging out...";
+
+ await apiRequest("/auth/logout", {
+ method: "POST"
+ });
+ } catch (error) {
+ console.warn("Logout failed:", error.message);
+ } finally {
+ localStorage.clear();
+ sessionStorage.clear();
+ window.location.href = "../../index.html";
+ }
+}
+
+async function loadAdminProfile() {
+ try {
+ let profile;
+ try {
+ profile = await apiRequest("/users/me");
+ } catch {
+ profile = await apiRequest("/user/me");
+ }
+
+ els.adminName.textContent =
+ profile.fullName ||
+ profile.name ||
+ "Admin User";
+
+ els.adminRole.textContent =
+ profile.role
+ ? profile.role.charAt(0).toUpperCase() + profile.role.slice(1)
+ : "Admin";
+
+ if (profile.profileImage) {
+ els.adminAvatar.src = profile.profileImage;
+ }
+ } catch (error) {
+ console.error("Failed to load admin profile:", error.message);
+ window.location.href = "../profile/admin-profile.html";
+ }
+}
+
+async function loadFlags() {
+ try {
+ const [usersPayload, eventsPayload] = await Promise.all([
+ apiRequest("/user").catch(() => apiRequest("/users")),
+ apiRequest("/events")
+ ]);
+
+ const users = normalizeUsers(usersPayload);
+ const events = normalizeEvents(eventsPayload);
+
+ flagsCache = buildFlags(users, events);
+ renderFlags();
+ } catch (error) {
+ console.error("Failed to load flags:", error.message);
+ els.flagsTable.innerHTML = `
+
+ Failed to load flags.
+
+ `;
+ }
+}
+
+function bindUI() {
+ els.searchInput.addEventListener("input", renderFlags);
+
+ bindFilters();
+
+ els.logoutBtn.addEventListener("click", openLogoutModal);
+ els.closeLogoutModal.addEventListener("click", closeLogoutModal);
+ els.cancelLogout.addEventListener("click", closeLogoutModal);
+ els.confirmLogout.addEventListener("click", handleLogout);
+
+ els.logoutModal.addEventListener("click", (event) => {
+ if (event.target === els.logoutModal) {
+ closeLogoutModal();
+ }
+ });
+
+ document.addEventListener("keydown", (event) => {
+ if (event.key === "Escape" && !els.logoutModal.classList.contains("hidden")) {
+ closeLogoutModal();
+ }
+ });
+}
+
+document.addEventListener("DOMContentLoaded", async () => {
+ bindUI();
+ await loadAdminProfile();
+ await loadFlags();
+});
\ No newline at end of file
diff --git a/Admin/login/admin-login.css b/Admin/login/admin-login.css
new file mode 100644
index 0000000..d328716
--- /dev/null
+++ b/Admin/login/admin-login.css
@@ -0,0 +1,210 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #edf2f6;
+ color: #172033;
+}
+
+.login-page {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1.5rem;
+}
+
+.login-card {
+ width: 100%;
+ max-width: 31rem;
+ background: #ffffff;
+ border-radius: 1rem;
+ padding: 2rem;
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.08);
+}
+
+.logo-wrap {
+ text-align: center;
+ margin-bottom: 1.5rem;
+}
+
+.logo {
+ width: 4rem;
+ height: 4rem;
+ object-fit: contain;
+ margin-bottom: 0.75rem;
+}
+
+.tagline {
+ font-size: 0.85rem;
+ color: #6b7280;
+}
+
+.login-card h1 {
+ font-size: 1.7rem;
+ font-weight: 700;
+ color: #172033;
+ margin-bottom: 1.5rem;
+ text-align: center;
+}
+
+.form-group {
+ margin-bottom: 1rem;
+}
+
+.form-group label {
+ display: block;
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: #223f6b;
+ margin-bottom: 0.45rem;
+}
+
+.form-group input {
+ width: 100%;
+ height: 3rem;
+ border: 0.0625rem solid #d9dde3;
+ border-radius: 0.55rem;
+ padding: 0 0.9rem;
+ font-size: 0.95rem;
+ outline: none;
+ background: #ffffff;
+ color: #374151;
+}
+
+.form-group input:focus {
+ border-color: #1d7de2;
+ box-shadow: 0 0 0 0.15rem rgba(29, 125, 226, 0.12);
+}
+
+.password-wrap {
+ position: relative;
+}
+
+.password-wrap input {
+ padding-right: 3rem;
+}
+
+.toggle-password {
+ position: absolute;
+ top: 50%;
+ right: 0.8rem;
+ transform: translateY(-50%);
+ border: none;
+ background: transparent;
+ color: #6b7280;
+ cursor: pointer;
+ font-size: 1rem;
+}
+
+.remember-wrap {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.55rem;
+ cursor: pointer;
+ user-select: none;
+ margin: 0.35rem 0 1rem;
+}
+
+.remember-wrap input {
+ display: none;
+}
+
+.custom-check {
+ width: 1rem;
+ height: 1rem;
+ border: 0.0625rem solid #c5ccd6;
+ border-radius: 0.2rem;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ background: #ffffff;
+ color: transparent;
+ font-size: 0.65rem;
+}
+
+.remember-wrap input:checked + .custom-check {
+ background: #1d7de2;
+ border-color: #1d7de2;
+ color: #ffffff;
+}
+
+.remember-text {
+ font-size: 0.85rem;
+ color: #4b5563;
+}
+
+.error-message,
+.success-message {
+ display: block;
+ min-height: 1rem;
+ font-size: 0.78rem;
+ margin-top: 0.35rem;
+}
+
+.error-message {
+ color: #dc2626;
+}
+
+.success-message {
+ color: #16a34a;
+}
+
+.form-error,
+.success-message {
+ text-align: center;
+ margin-bottom: 0.5rem;
+}
+
+.button-group {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 0.8rem;
+ margin-top: 0.5rem;
+}
+
+.btn {
+ height: 3rem;
+ border: none;
+ border-radius: 0.55rem;
+ font-size: 0.92rem;
+ font-weight: 600;
+ cursor: pointer;
+}
+
+.btn-primary {
+ background: #1d7de2;
+ color: #ffffff;
+}
+
+.btn-primary:hover {
+ opacity: 0.94;
+}
+
+.btn-secondary {
+ background: #e5e7eb;
+ color: #172033;
+}
+
+.btn-secondary:hover {
+ background: #dfe3e8;
+}
+
+.btn:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+}
+
+@media (max-width: 32rem) {
+ .login-card {
+ padding: 1.3rem;
+ }
+
+ .button-group {
+ grid-template-columns: 1fr;
+ }
+}
\ No newline at end of file
diff --git a/Admin/login/admin-login.html b/Admin/login/admin-login.html
new file mode 100644
index 0000000..c821120
--- /dev/null
+++ b/Admin/login/admin-login.html
@@ -0,0 +1,81 @@
+
+
+
+
+
+ AIDLoop Admin Login
+
+
+
+
+
+
+
+
+
+
+
+
+
Connecting Volunteers to verified community events
+
+
+ Log in
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Admin/login/admin-login.js b/Admin/login/admin-login.js
new file mode 100644
index 0000000..ff31099
--- /dev/null
+++ b/Admin/login/admin-login.js
@@ -0,0 +1,147 @@
+const API_BASE_URL = "https://aidloop-backend.onrender.com/api";
+
+const els = {
+ loginForm: document.getElementById("loginForm"),
+ email: document.getElementById("email"),
+ password: document.getElementById("password"),
+ rememberMe: document.getElementById("rememberMe"),
+ loginBtn: document.getElementById("loginBtn"),
+ forgotPasswordBtn: document.getElementById("forgotPasswordBtn"),
+ togglePassword: document.getElementById("togglePassword"),
+ emailError: document.getElementById("emailError"),
+ passwordError: document.getElementById("passwordError"),
+ formError: document.getElementById("formError"),
+ formSuccess: document.getElementById("formSuccess")
+};
+
+async function apiRequest(endpoint, options = {}) {
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ ...(options.headers || {})
+ },
+ ...options
+ });
+
+ const contentType = response.headers.get("content-type") || "";
+ const data = contentType.includes("application/json")
+ ? await response.json()
+ : await response.text();
+
+ if (!response.ok) {
+ throw new Error(
+ (data && data.message) ||
+ (data && data.error) ||
+ "Request failed"
+ );
+ }
+
+ return data;
+}
+
+function clearErrors() {
+ els.emailError.textContent = "";
+ els.passwordError.textContent = "";
+ els.formError.textContent = "";
+ els.formSuccess.textContent = "";
+}
+
+function validateEmail(value) {
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
+}
+
+function validateForm() {
+ clearErrors();
+
+ const email = els.email.value.trim();
+ const password = els.password.value.trim();
+ let isValid = true;
+
+ if (!email) {
+ els.emailError.textContent = "Email address is required.";
+ isValid = false;
+ } else if (!validateEmail(email)) {
+ els.emailError.textContent = "Enter a valid email address.";
+ isValid = false;
+ }
+
+ if (!password) {
+ els.passwordError.textContent = "Password is required.";
+ isValid = false;
+ }
+
+ return isValid;
+}
+
+function restoreRememberedEmail() {
+ const rememberedEmail = localStorage.getItem("aidloop_admin_email");
+ if (rememberedEmail) {
+ els.email.value = rememberedEmail;
+ els.rememberMe.checked = true;
+ }
+}
+
+function togglePasswordVisibility() {
+ const isPassword = els.password.type === "password";
+ els.password.type = isPassword ? "text" : "password";
+
+ els.togglePassword.innerHTML = isPassword
+ ? ' '
+ : ' ';
+}
+
+async function handleLogin(event) {
+ event.preventDefault();
+
+ if (!validateForm()) return;
+
+ try {
+ clearErrors();
+ els.loginBtn.disabled = true;
+ els.loginBtn.textContent = "Logging in...";
+
+ const payload = await apiRequest("/auth/login", {
+ method: "POST",
+ body: JSON.stringify({
+ email: els.email.value.trim(),
+ password: els.password.value.trim()
+ })
+ });
+
+ const role = String(payload?.user?.role || payload?.role || "").toLowerCase();
+
+ if (role && role !== "admin") {
+ throw new Error("This account is not an admin account.");
+ }
+
+ if (els.rememberMe.checked) {
+ localStorage.setItem("aidloop_admin_email", els.email.value.trim());
+ } else {
+ localStorage.removeItem("aidloop_admin_email");
+ }
+
+ els.formSuccess.textContent = payload.message || "Login successful.";
+
+ setTimeout(() => {
+ window.location.href = "../dashboard/admin-dashboard.html";
+ }, 800);
+ } catch (error) {
+ els.formError.textContent = error.message || "Login failed.";
+ } finally {
+ els.loginBtn.disabled = false;
+ els.loginBtn.textContent = "Log in";
+ }
+}
+
+function handleForgotPassword() {
+ clearErrors();
+ els.formError.textContent =
+ "No admin forgot-password endpoint has been provided yet.";
+}
+
+els.loginForm.addEventListener("submit", handleLogin);
+els.togglePassword.addEventListener("click", togglePasswordVisibility);
+els.forgotPasswordBtn.addEventListener("click", handleForgotPassword);
+
+document.addEventListener("DOMContentLoaded", restoreRememberedEmail);
\ No newline at end of file
diff --git a/Admin/organizations/organization-details.css b/Admin/organizations/organization-details.css
new file mode 100644
index 0000000..3d1c6d4
--- /dev/null
+++ b/Admin/organizations/organization-details.css
@@ -0,0 +1,149 @@
+ * {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #edf2f6;
+ color: #172033;
+}
+
+.overlay {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1.5rem;
+}
+
+.modal-card {
+ width: 100%;
+ max-width: 42rem;
+ background: #ffffff;
+ border-radius: 1rem;
+ padding: 1.8rem;
+ position: relative;
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.12);
+}
+
+.close-btn {
+ position: absolute;
+ top: 0.8rem;
+ right: 1rem;
+ border: none;
+ background: transparent;
+ color: #6b7280;
+ font-size: 1.5rem;
+ cursor: pointer;
+}
+
+.close-btn:hover {
+ color: #172033;
+}
+
+.modal-header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 1rem;
+ margin-bottom: 1.5rem;
+}
+
+.modal-header h1 {
+ font-size: 1.6rem;
+ font-weight: 700;
+ color: #172033;
+ line-height: 1.3;
+}
+
+.status-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 7rem;
+ padding: 0.45rem 0.85rem;
+ border-radius: 0.55rem;
+ color: #ffffff;
+ font-size: 0.72rem;
+ font-weight: 700;
+ text-transform: capitalize;
+}
+
+.status-badge.awaiting {
+ background: #f59e0b;
+}
+
+.status-badge.verified {
+ background: #16a34a;
+}
+
+.status-badge.rejected {
+ background: #dc2626;
+}
+
+.details-grid {
+ display: grid;
+ grid-template-columns: 8rem 1fr;
+ gap: 0.9rem 1rem;
+ margin-bottom: 1.5rem;
+}
+
+.label {
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: #223f6b;
+}
+
+.value {
+ font-size: 0.9rem;
+ color: #374151;
+ word-break: break-word;
+}
+
+.description-section h2 {
+ font-size: 1rem;
+ font-weight: 700;
+ margin-bottom: 0.7rem;
+ color: #172033;
+}
+
+.description-box {
+ background: #f8fafc;
+ border: 0.0625rem solid #e5e7eb;
+ border-radius: 0.75rem;
+ padding: 1rem;
+ font-size: 0.9rem;
+ line-height: 1.7;
+ color: #374151;
+}
+
+.social-link {
+ color: #1d7de2;
+ text-decoration: none;
+}
+
+.social-link:hover {
+ text-decoration: underline;
+}
+
+@media (max-width: 36rem) {
+ .modal-card {
+ padding: 1.2rem;
+ }
+
+ .modal-header {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .details-grid {
+ grid-template-columns: 1fr;
+ gap: 0.4rem;
+ }
+
+ .label {
+ margin-top: 0.4rem;
+ }
+}
\ No newline at end of file
diff --git a/Admin/organizations/organization-details.html b/Admin/organizations/organization-details.html
new file mode 100644
index 0000000..f68ad8b
--- /dev/null
+++ b/Admin/organizations/organization-details.html
@@ -0,0 +1,51 @@
+
+
+
+
+
+ Organizer Details
+
+
+
+
+
+
+
+
+
+
×
+
+
+
+
+
Org. Name:
+
Loading...
+
+
Social Links:
+
—
+
+
Email:
+
—
+
+
Phone No:
+
—
+
+
Location:
+
—
+
+
+
+
Description
+
+
Loading organization description...
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Admin/organizations/organization-details.js b/Admin/organizations/organization-details.js
new file mode 100644
index 0000000..d717ced
--- /dev/null
+++ b/Admin/organizations/organization-details.js
@@ -0,0 +1,183 @@
+const API_BASE_URL = "https://aidloop-backend.onrender.com/api";
+
+const els = {
+ closeBtn: document.getElementById("closeBtn"),
+ orgTitle: document.getElementById("orgTitle"),
+ statusBadge: document.getElementById("statusBadge"),
+ orgName: document.getElementById("orgName"),
+ socialLinks: document.getElementById("socialLinks"),
+ email: document.getElementById("email"),
+ phoneNumber: document.getElementById("phoneNumber"),
+ location: document.getElementById("location"),
+ description: document.getElementById("description")
+};
+
+const organizerId = new URLSearchParams(window.location.search).get("id");
+
+async function apiRequest(endpoint, options = {}) {
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ ...(options.headers || {})
+ },
+ ...options
+ });
+
+ const contentType = response.headers.get("content-type") || "";
+ const data = contentType.includes("application/json")
+ ? await response.json()
+ : await response.text();
+
+ if (!response.ok) {
+ throw new Error(
+ (data && data.message) ||
+ (data && data.error) ||
+ "Request failed"
+ );
+ }
+
+ return data;
+}
+
+function normalizeUsers(payload) {
+ if (Array.isArray(payload)) return payload;
+ if (Array.isArray(payload?.users)) return payload.users;
+ if (Array.isArray(payload?.data)) return payload.data;
+ return [];
+}
+
+function getDisplayName(user) {
+ return user.fullName || user.name || user.organizationName || "Organization";
+}
+
+function getLocation(user) {
+ if (typeof user.location === "string" && user.location.trim()) {
+ return user.location;
+ }
+
+ if (user.location && typeof user.location === "object") {
+ return (
+ [user.location.venue, user.location.city || user.location.state]
+ .filter(Boolean)
+ .join(", ") || "—"
+ );
+ }
+
+ return user.city || user.state || "—";
+}
+
+function getOrganizerStatus(user) {
+ const status = String(user.status || "").toLowerCase();
+ const approvalStatus = String(user.approvalStatus || "").toLowerCase();
+ const isVerified = Boolean(user.isVerified);
+
+ if (status === "rejected" || approvalStatus === "rejected") return "rejected";
+
+ if (
+ status === "verified" ||
+ status === "approved" ||
+ approvalStatus === "verified" ||
+ approvalStatus === "approved" ||
+ isVerified
+ ) {
+ return "verified";
+ }
+
+ return "awaiting";
+}
+
+function setStatusBadge(status) {
+ els.statusBadge.textContent =
+ status === "verified"
+ ? "Verified"
+ : status === "rejected"
+ ? "Rejected"
+ : "Awaiting";
+
+ els.statusBadge.className = "status-badge";
+ els.statusBadge.classList.add(status);
+}
+
+function renderSocialLinks(user) {
+ const link =
+ user.website ||
+ user.socialLink ||
+ user.socialLinks?.[0] ||
+ "";
+
+ if (!link) {
+ els.socialLinks.textContent = "—";
+ return;
+ }
+
+ els.socialLinks.innerHTML = `
+
+ ${link}
+
+ `;
+}
+
+function populateOrganizer(user) {
+ const status = getOrganizerStatus(user);
+
+ els.orgTitle.textContent = getDisplayName(user);
+ els.orgName.textContent = getDisplayName(user);
+ els.email.textContent = user.email || "—";
+ els.phoneNumber.textContent = user.phoneNumber || user.phone || "—";
+ els.location.textContent = getLocation(user);
+ els.description.innerHTML = `${
+ user.description ||
+ user.bio ||
+ "No organization description available."
+ }
`;
+
+ renderSocialLinks(user);
+ setStatusBadge(status);
+}
+
+async function loadOrganizerDetails() {
+ if (!organizerId) {
+ els.orgTitle.textContent = "No organizer selected";
+ els.description.innerHTML = "No organizer ID provided.
";
+ return;
+ }
+
+ try {
+ let payload;
+
+ try {
+ payload = await apiRequest("/user");
+ } catch {
+ payload = await apiRequest("/users");
+ }
+
+ const users = normalizeUsers(payload);
+
+ const organizer = users.find(
+ (user) => String(user._id || user.id) === String(organizerId)
+ );
+
+ if (!organizer) {
+ throw new Error("Organizer not found");
+ }
+
+ populateOrganizer(organizer);
+ } catch (error) {
+ els.orgTitle.textContent = "Unable to load organizer";
+ els.description.innerHTML = `${error.message || "Failed to fetch organizer details."}
`;
+ }
+}
+
+function closeModal() {
+ if (window.history.length > 1) {
+ window.history.back();
+ return;
+ }
+
+ window.location.href = "organization-directory.html";
+}
+
+els.closeBtn.addEventListener("click", closeModal);
+
+document.addEventListener("DOMContentLoaded", loadOrganizerDetails);
\ No newline at end of file
diff --git a/Admin/organizations/organization-directory.css b/Admin/organizations/organization-directory.css
new file mode 100644
index 0000000..f1bff77
--- /dev/null
+++ b/Admin/organizations/organization-directory.css
@@ -0,0 +1,453 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #edf2f6;
+ color: #172033;
+}
+
+.dashboard {
+ display: flex;
+ min-height: 100vh;
+}
+
+.sidebar {
+ width: 15rem;
+ background: #223f6b;
+ color: #ffffff;
+ padding: 1.8rem 0.9rem;
+}
+
+.sidebar-logo {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.35rem;
+ margin-bottom: 2.4rem;
+}
+
+.sidebar-logo img {
+ width: 3.1rem;
+ height: 3.1rem;
+ object-fit: contain;
+}
+
+.sidebar-menu {
+ list-style: none;
+}
+
+.sidebar-menu li {
+ margin-bottom: 0.8rem;
+}
+
+.sidebar-menu li a,
+.logout-btn {
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+ text-decoration: none;
+ color: #ffffff;
+ padding: 0.9rem 0.85rem;
+ border-radius: 0.45rem;
+ font-size: 0.95rem;
+ transition: background 0.2s ease;
+ width: 100%;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+}
+
+.sidebar-menu li a i,
+.logout-btn i {
+ width: 1rem;
+ text-align: center;
+}
+
+.sidebar-menu li.active a,
+.sidebar-menu li a:hover {
+ background: #1d7de2;
+}
+
+.logout-btn:hover {
+ background: #ef2f2f;
+}
+
+.main-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.topbar {
+ height: 5.8rem;
+ background: #ffffff;
+ border-bottom: 0.0625rem solid #e6e8eb;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 1.8rem;
+}
+
+.search-box {
+ width: 21rem;
+ height: 2.9rem;
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ border: 0.0625rem solid #d9dde3;
+ border-radius: 0.35rem;
+ padding: 0 0.9rem;
+ background: #ffffff;
+}
+
+.search-box i {
+ color: #6b7280;
+ font-size: 0.95rem;
+}
+
+.search-box input {
+ width: 100%;
+ border: none;
+ outline: none;
+ background: transparent;
+ font-size: 0.95rem;
+ color: #374151;
+}
+
+.topbar-right {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.notification {
+ position: relative;
+ color: #111827;
+ font-size: 1.2rem;
+}
+
+.notification-dot {
+ position: absolute;
+ top: 0.08rem;
+ right: -0.12rem;
+ width: 0.45rem;
+ height: 0.45rem;
+ background: #ef2f2f;
+ border-radius: 50%;
+}
+
+.admin-profile {
+ display: flex;
+ align-items: center;
+ gap: 0.7rem;
+}
+
+.user-avatar {
+ width: 2.45rem;
+ height: 2.45rem;
+ border-radius: 50%;
+ object-fit: cover;
+}
+
+.admin-text h4 {
+ font-size: 0.82rem;
+ font-weight: 600;
+ color: #172033;
+ line-height: 1.2;
+}
+
+.admin-text p {
+ font-size: 0.72rem;
+ color: #6b7280;
+ line-height: 1.2;
+}
+
+.admin-chevron {
+ color: #6b7280;
+ font-size: 0.7rem;
+}
+
+.page-content {
+ padding: 4rem 1.4rem 2rem;
+}
+
+.page-header {
+ margin-bottom: 2rem;
+}
+
+.page-header h1 {
+ font-size: 2.1rem;
+ font-weight: 700;
+ margin-bottom: 0.3rem;
+}
+
+.page-header p {
+ font-size: 0.78rem;
+ color: #6b7280;
+}
+
+.filter-buttons {
+ display: flex;
+ gap: 0.8rem;
+ margin-bottom: 1.4rem;
+ flex-wrap: wrap;
+}
+
+.filter-btn {
+ border: 0.0625rem solid #d9dde3;
+ background: #ffffff;
+ color: #374151;
+ padding: 0.75rem 1rem;
+ border-radius: 0.4rem;
+ font-size: 0.8rem;
+ cursor: pointer;
+}
+
+.filter-btn.active {
+ background: #1d7de2;
+ color: #ffffff;
+ border-color: #1d7de2;
+}
+
+.table-wrapper {
+ background: #ffffff;
+ overflow: hidden;
+ border-radius: 0.6rem;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+thead {
+ background: #f4f6f8;
+}
+
+th,
+td {
+ text-align: left;
+ padding: 1rem 1.4rem;
+}
+
+th {
+ font-size: 0.62rem;
+ letter-spacing: 0.08em;
+ color: #7b8492;
+ font-weight: 700;
+}
+
+td {
+ font-size: 0.72rem;
+ color: #172033;
+ border-top: 0.0625rem solid #eef1f3;
+}
+
+.status-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 5.5rem;
+ padding: 0.35rem 0.7rem;
+ border-radius: 0.45rem;
+ color: #ffffff;
+ font-size: 0.58rem;
+ font-weight: 700;
+}
+
+.status-badge.verified {
+ background: #16a34a;
+}
+
+.status-badge.rejected {
+ background: #dc2626;
+}
+
+.status-badge.pending {
+ background: #f59e0b;
+}
+
+.action-link {
+ color: #1d7de2;
+ text-decoration: none;
+ font-weight: 600;
+}
+
+.action-link:hover {
+ text-decoration: underline;
+}
+
+.empty-state {
+ text-align: center;
+ padding: 3rem 1rem;
+}
+
+.empty-icon {
+ width: 4rem;
+ height: 4rem;
+ margin: 0 auto 1rem;
+ border-radius: 50%;
+ background: #e8f0fb;
+ color: #1d7de2;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ font-size: 1.4rem;
+}
+
+.empty-check {
+ position: absolute;
+ right: -0.2rem;
+ bottom: -0.2rem;
+ font-size: 0.9rem;
+ color: #16a34a;
+}
+
+.empty-state h2 {
+ font-size: 1.1rem;
+ margin-bottom: 0.4rem;
+}
+
+.empty-state p {
+ color: #6b7280;
+ font-size: 0.85rem;
+}
+
+.hidden {
+ display: none !important;
+}
+
+.modal-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(15, 23, 42, 0.45);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1rem;
+ z-index: 999;
+}
+
+.modal-card {
+ width: 100%;
+ max-width: 25rem;
+ background: #ffffff;
+ border-radius: 1rem;
+ padding: 1.5rem;
+ position: relative;
+ text-align: center;
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.15);
+}
+
+.modal-close-btn {
+ position: absolute;
+ top: 0.7rem;
+ right: 0.9rem;
+ border: none;
+ background: transparent;
+ font-size: 1.5rem;
+ color: #6b7280;
+ cursor: pointer;
+}
+
+.modal-icon-wrap {
+ width: 3.5rem;
+ height: 3.5rem;
+ margin: 0 auto 1rem;
+ border-radius: 50%;
+ background: #fee2e2;
+ color: #dc2626;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.25rem;
+}
+
+.modal-card h3 {
+ font-size: 1.2rem;
+ margin-bottom: 0.5rem;
+ color: #172033;
+}
+
+.modal-card p {
+ font-size: 0.92rem;
+ color: #6b7280;
+ margin-bottom: 1.3rem;
+}
+
+.modal-actions {
+ display: flex;
+ gap: 0.8rem;
+ justify-content: center;
+}
+
+.modal-btn {
+ border: none;
+ border-radius: 0.55rem;
+ padding: 0.8rem 1rem;
+ font-size: 0.9rem;
+ cursor: pointer;
+ min-width: 8rem;
+}
+
+.modal-btn.secondary {
+ background: #e5e7eb;
+ color: #172033;
+}
+
+.modal-btn.danger {
+ background: #dc2626;
+ color: #ffffff;
+}
+
+@media (max-width: 75rem) {
+ .table-wrapper {
+ overflow-x: auto;
+ }
+
+ table {
+ min-width: 46rem;
+ }
+}
+
+@media (max-width: 62rem) {
+ .sidebar {
+ display: none;
+ }
+
+ .topbar {
+ padding: 0 1rem;
+ }
+
+ .search-box {
+ width: 16rem;
+ }
+
+ .page-content {
+ padding: 2rem 1rem;
+ }
+
+ .admin-text,
+ .admin-chevron {
+ display: none;
+ }
+}
+
+@media (max-width: 36rem) {
+ .search-box {
+ width: 12rem;
+ }
+
+ .modal-actions {
+ flex-direction: column;
+ }
+
+ .modal-btn {
+ width: 100%;
+ }
+}
diff --git a/Admin/organizations/organization-directory.html b/Admin/organizations/organization-directory.html
new file mode 100644
index 0000000..0a87836
--- /dev/null
+++ b/Admin/organizations/organization-directory.html
@@ -0,0 +1,153 @@
+
+
+
+
+
+ AIDLoop - Organizer Directory
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Status: All
+
+ Verified
+ Rejected
+
+
+
+
+
+
+ ORGANIZATION NAME
+ CONTACT EMAIL
+ LOCATION
+ STATUS
+ ACTIONS
+
+
+
+
+
+ Loading organizations...
+
+
+
+
+
+
+
+
+
+
No Organizations Found
+
Registered organizations will appear here once they sign up.
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
Log out?
+
You are about to end your current admin session.
+
+
+ Cancel
+ Yes, Log out
+
+
+
+
+
+
+
diff --git a/Admin/organizations/organization-directory.js b/Admin/organizations/organization-directory.js
new file mode 100644
index 0000000..e33acd7
--- /dev/null
+++ b/Admin/organizations/organization-directory.js
@@ -0,0 +1,260 @@
+const API_BASE_URL = "https://aidloop-backend.onrender.com/api";
+
+const els = {
+ adminName: document.getElementById("adminName"),
+ adminRole: document.getElementById("adminRole"),
+ adminAvatar: document.getElementById("adminAvatar"),
+ directoryTable: document.getElementById("directoryTable"),
+ directoryTableWrap: document.getElementById("directoryTableWrap"),
+ emptyState: document.getElementById("emptyState"),
+ searchInput: document.getElementById("searchInput"),
+ filterButtons: document.querySelectorAll(".filter-btn"),
+ logoutBtn: document.getElementById("logoutBtn"),
+ logoutModal: document.getElementById("logoutModal"),
+ closeLogoutModal: document.getElementById("closeLogoutModal"),
+ cancelLogout: document.getElementById("cancelLogout"),
+ confirmLogout: document.getElementById("confirmLogout")
+};
+
+let organizersCache = [];
+let currentFilter = "all";
+
+async function apiRequest(endpoint, options = {}) {
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ ...(options.headers || {})
+ },
+ ...options
+ });
+
+ const contentType = response.headers.get("content-type") || "";
+ const data = contentType.includes("application/json")
+ ? await response.json()
+ : await response.text();
+
+ if (!response.ok) {
+ throw new Error(
+ (data && data.message) ||
+ (data && data.error) ||
+ "Request failed"
+ );
+ }
+
+ return data;
+}
+
+function normalizeUsers(payload) {
+ if (Array.isArray(payload)) return payload;
+ if (Array.isArray(payload?.users)) return payload.users;
+ if (Array.isArray(payload?.data)) return payload.data;
+ return [];
+}
+
+function getOrganizerStatus(user) {
+ const status = String(user.status || "").toLowerCase();
+ const approvalStatus = String(user.approvalStatus || "").toLowerCase();
+ const isVerified = Boolean(user.isVerified);
+
+ if (status === "rejected" || approvalStatus === "rejected") return "rejected";
+
+ if (
+ status === "verified" ||
+ status === "approved" ||
+ approvalStatus === "verified" ||
+ approvalStatus === "approved" ||
+ isVerified
+ ) {
+ return "verified";
+ }
+
+ return "pending";
+}
+
+function getDisplayName(user) {
+ return user.fullName || user.name || user.organizationName || "Unnamed Organizer";
+}
+
+function getLocation(user) {
+ if (typeof user.location === "string" && user.location.trim()) {
+ return user.location;
+ }
+
+ if (user.location && typeof user.location === "object") {
+ return (
+ [user.location.venue, user.location.city || user.location.state]
+ .filter(Boolean)
+ .join(", ") || "—"
+ );
+ }
+
+ return user.city || user.state || "—";
+}
+
+function renderDirectory() {
+ const query = els.searchInput.value.trim().toLowerCase();
+
+ let filtered = [...organizersCache];
+
+ if (currentFilter !== "all") {
+ filtered = filtered.filter((organizer) => organizer._status === currentFilter);
+ }
+
+ if (query) {
+ filtered = filtered.filter((organizer) => {
+ const searchableText = `
+ ${getDisplayName(organizer)}
+ ${organizer.email || ""}
+ ${getLocation(organizer)}
+ ${organizer._status}
+ `.toLowerCase();
+
+ return searchableText.includes(query);
+ });
+ }
+
+ if (!filtered.length) {
+ els.directoryTableWrap.style.display = "none";
+ els.emptyState.style.display = "block";
+ return;
+ }
+
+ els.directoryTableWrap.style.display = "table";
+ els.emptyState.style.display = "none";
+
+ els.directoryTable.innerHTML = filtered.map((organizer) => `
+
+ ${getDisplayName(organizer)}
+ ${organizer.email || "—"}
+ ${getLocation(organizer)}
+
+
+ ${organizer._status.charAt(0).toUpperCase() + organizer._status.slice(1)}
+
+
+
+
+ View Details
+
+
+
+ `).join("");
+}
+
+function bindFilters() {
+ els.filterButtons.forEach((button) => {
+ button.addEventListener("click", () => {
+ els.filterButtons.forEach((btn) => btn.classList.remove("active"));
+ button.classList.add("active");
+ currentFilter = button.dataset.filter;
+ renderDirectory();
+ });
+ });
+}
+
+function openLogoutModal() {
+ els.logoutModal.classList.remove("hidden");
+}
+
+function closeLogoutModal() {
+ els.logoutModal.classList.add("hidden");
+ els.confirmLogout.disabled = false;
+ els.confirmLogout.textContent = "Yes, Log out";
+}
+
+async function handleLogout() {
+ try {
+ els.confirmLogout.disabled = true;
+ els.confirmLogout.textContent = "Logging out...";
+
+ await apiRequest("/auth/logout", {
+ method: "POST"
+ });
+ } catch (error) {
+ console.warn("Logout failed:", error.message);
+ } finally {
+ localStorage.clear();
+ sessionStorage.clear();
+ window.location.href = "../../index.html";
+ }
+}
+
+async function loadAdminProfile() {
+ try {
+ let profile;
+ try {
+ profile = await apiRequest("/users/me");
+ } catch {
+ profile = await apiRequest("/user/me");
+ }
+
+ els.adminName.textContent =
+ profile.fullName ||
+ profile.name ||
+ "Admin User";
+
+ els.adminRole.textContent =
+ profile.role
+ ? profile.role.charAt(0).toUpperCase() + profile.role.slice(1)
+ : "Admin";
+
+ if (profile.profileImage) {
+ els.adminAvatar.src = profile.profileImage;
+ }
+ } catch (error) {
+ console.error("Failed to load admin profile:", error.message);
+ window.location.href = "../profile/admin-profile.html";
+ }
+}
+
+async function loadOrganizations() {
+ try {
+ const payload = await apiRequest("/user").catch(() => apiRequest("/users"));
+ const users = normalizeUsers(payload);
+
+ organizersCache = users
+ .filter((user) => String(user.role || "").toLowerCase() === "organizer")
+ .map((user) => ({
+ ...user,
+ _status: getOrganizerStatus(user)
+ }));
+
+ renderDirectory();
+ } catch (error) {
+ console.error("Failed to load organizations:", error.message);
+ els.directoryTable.innerHTML = `
+
+ Failed to load organizations.
+
+ `;
+ }
+}
+
+function bindUI() {
+ els.searchInput.addEventListener("input", renderDirectory);
+ bindFilters();
+
+ els.logoutBtn.addEventListener("click", openLogoutModal);
+ els.closeLogoutModal.addEventListener("click", closeLogoutModal);
+ els.cancelLogout.addEventListener("click", closeLogoutModal);
+ els.confirmLogout.addEventListener("click", handleLogout);
+
+ els.logoutModal.addEventListener("click", (event) => {
+ if (event.target === els.logoutModal) {
+ closeLogoutModal();
+ }
+ });
+
+ document.addEventListener("keydown", (event) => {
+ if (event.key === "Escape" && !els.logoutModal.classList.contains("hidden")) {
+ closeLogoutModal();
+ }
+ });
+}
+
+document.addEventListener("DOMContentLoaded", async () => {
+ bindUI();
+ await loadAdminProfile();
+ await loadOrganizations();
+});
\ No newline at end of file
diff --git a/Admin/profile/admin-profile.css b/Admin/profile/admin-profile.css
new file mode 100644
index 0000000..20c9749
--- /dev/null
+++ b/Admin/profile/admin-profile.css
@@ -0,0 +1,418 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #edf2f6;
+ color: #172033;
+}
+
+.dashboard {
+ display: flex;
+ min-height: 100vh;
+}
+
+.sidebar {
+ width: 15rem;
+ background: #223f6b;
+ color: #ffffff;
+ padding: 1.8rem 0.9rem;
+}
+
+.sidebar-logo {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 2.4rem;
+}
+
+.sidebar-logo img {
+ width: 3.1rem;
+ height: 3.1rem;
+ object-fit: contain;
+}
+
+.sidebar-menu {
+ list-style: none;
+}
+
+.sidebar-menu li {
+ margin-bottom: 0.8rem;
+}
+
+.sidebar-menu li a,
+.logout-btn {
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+ text-decoration: none;
+ color: #ffffff;
+ padding: 0.9rem 0.85rem;
+ border-radius: 0.45rem;
+ font-size: 0.95rem;
+ transition: background 0.2s ease;
+ width: 100%;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+}
+
+.sidebar-menu li a i,
+.logout-btn i {
+ width: 1rem;
+ text-align: center;
+}
+
+.sidebar-menu li.active a,
+.sidebar-menu li a:hover {
+ background: #1d7de2;
+}
+
+.logout-btn:hover {
+ background: #ef2f2f;
+}
+
+.main-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.topbar {
+ height: 5.8rem;
+ background: #ffffff;
+ border-bottom: 0.0625rem solid #e6e8eb;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 1.8rem;
+}
+
+.search-box {
+ width: 21rem;
+ height: 2.9rem;
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ border: 0.0625rem solid #d9dde3;
+ border-radius: 0.35rem;
+ padding: 0 0.9rem;
+ background: #ffffff;
+}
+
+.search-box i {
+ color: #6b7280;
+ font-size: 0.95rem;
+}
+
+.search-box input {
+ width: 100%;
+ border: none;
+ outline: none;
+ background: transparent;
+ font-size: 0.95rem;
+ color: #374151;
+}
+
+.topbar-right {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.notification {
+ position: relative;
+ color: #111827;
+ font-size: 1.2rem;
+}
+
+.notification-dot {
+ position: absolute;
+ top: 0.08rem;
+ right: -0.12rem;
+ width: 0.45rem;
+ height: 0.45rem;
+ background: #ef2f2f;
+ border-radius: 50%;
+}
+
+.admin-profile-mini {
+ display: flex;
+ align-items: center;
+ gap: 0.7rem;
+}
+
+.user-avatar {
+ width: 2.45rem;
+ height: 2.45rem;
+ border-radius: 50%;
+ object-fit: cover;
+}
+
+.admin-mini-text h4 {
+ font-size: 0.82rem;
+ font-weight: 600;
+ color: #172033;
+ line-height: 1.2;
+}
+
+.admin-mini-text p {
+ font-size: 0.72rem;
+ color: #6b7280;
+ line-height: 1.2;
+}
+
+.page-content {
+ padding: 4rem 1.4rem 2rem;
+}
+
+.page-header {
+ margin-bottom: 2rem;
+}
+
+.page-header h1 {
+ font-size: 2.1rem;
+ font-weight: 700;
+ margin-bottom: 0.3rem;
+}
+
+.page-header p {
+ font-size: 0.82rem;
+ color: #6b7280;
+}
+
+.profile-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1.4rem;
+}
+
+.card {
+ background: #ffffff;
+ border-radius: 0.85rem;
+ padding: 1.4rem;
+}
+
+.card h2 {
+ font-size: 1.1rem;
+ margin-bottom: 1rem;
+ color: #223f6b;
+}
+
+.form {
+ display: flex;
+ flex-direction: column;
+ gap: 0.9rem;
+}
+
+.form-group {
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+}
+
+.form-group label {
+ font-size: 0.85rem;
+ font-weight: 600;
+ color: #374151;
+}
+
+.form-group input {
+ width: 100%;
+ height: 3rem;
+ border: 0.0625rem solid #d9dde3;
+ border-radius: 0.5rem;
+ padding: 0 0.9rem;
+ font-size: 0.92rem;
+ color: #172033;
+ background: #ffffff;
+ outline: none;
+}
+
+.form-group input[readonly] {
+ background: #f8fafc;
+}
+
+.button-row {
+ display: flex;
+ justify-content: flex-start;
+ margin-top: 0.3rem;
+}
+
+.button-row.align-right {
+ justify-content: flex-end;
+}
+
+.primary-btn,
+.secondary-btn {
+ border: none;
+ border-radius: 0.55rem;
+ padding: 0.85rem 1.2rem;
+ font-size: 0.9rem;
+ cursor: pointer;
+}
+
+.primary-btn {
+ background: #1d7de2;
+ color: #ffffff;
+}
+
+.secondary-btn {
+ background: #223f6b;
+ color: #ffffff;
+}
+
+.primary-btn:hover,
+.secondary-btn:hover {
+ opacity: 0.94;
+}
+
+.primary-btn:disabled,
+.secondary-btn:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+}
+
+.feedback {
+ min-height: 1.1rem;
+ font-size: 0.82rem;
+}
+
+.feedback.success {
+ color: #16a34a;
+}
+
+.feedback.error {
+ color: #dc2626;
+}
+
+.hidden {
+ display: none !important;
+}
+
+.modal-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(15, 23, 42, 0.45);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1rem;
+ z-index: 999;
+}
+
+.modal-card {
+ width: 100%;
+ max-width: 25rem;
+ background: #ffffff;
+ border-radius: 1rem;
+ padding: 1.5rem;
+ position: relative;
+ text-align: center;
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.15);
+}
+
+.modal-close-btn {
+ position: absolute;
+ top: 0.7rem;
+ right: 0.9rem;
+ border: none;
+ background: transparent;
+ font-size: 1.5rem;
+ color: #6b7280;
+ cursor: pointer;
+}
+
+.modal-icon-wrap {
+ width: 3.5rem;
+ height: 3.5rem;
+ margin: 0 auto 1rem;
+ border-radius: 50%;
+ background: #fee2e2;
+ color: #dc2626;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.25rem;
+}
+
+.modal-card h3 {
+ font-size: 1.2rem;
+ margin-bottom: 0.5rem;
+ color: #172033;
+}
+
+.modal-card p {
+ font-size: 0.92rem;
+ color: #6b7280;
+ margin-bottom: 1.3rem;
+}
+
+.modal-actions {
+ display: flex;
+ gap: 0.8rem;
+ justify-content: center;
+}
+
+.modal-btn {
+ border: none;
+ border-radius: 0.55rem;
+ padding: 0.8rem 1rem;
+ font-size: 0.9rem;
+ cursor: pointer;
+ min-width: 8rem;
+}
+
+.modal-btn.secondary {
+ background: #e5e7eb;
+ color: #172033;
+}
+
+.modal-btn.danger {
+ background: #dc2626;
+ color: #ffffff;
+}
+
+@media (max-width: 68rem) {
+ .profile-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 62rem) {
+ .sidebar {
+ display: none;
+ }
+
+ .topbar {
+ padding: 0 1rem;
+ }
+
+ .search-box {
+ width: 16rem;
+ }
+
+ .page-content {
+ padding: 2rem 1rem;
+ }
+
+ .admin-mini-text {
+ display: none;
+ }
+}
+
+@media (max-width: 36rem) {
+ .search-box {
+ width: 12rem;
+ }
+
+ .modal-actions {
+ flex-direction: column;
+ }
+
+ .modal-btn {
+ width: 100%;
+ }
+}
\ No newline at end of file
diff --git a/Admin/profile/admin-profile.html b/Admin/profile/admin-profile.html
new file mode 100644
index 0000000..5e9f539
--- /dev/null
+++ b/Admin/profile/admin-profile.html
@@ -0,0 +1,174 @@
+
+
+
+
+
+ AIDLoop - Admin Profile
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Profile Information
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
Log out?
+
You are about to end your current admin session.
+
+
+ Cancel
+ Yes, Log out
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Admin/profile/admin-profile.js b/Admin/profile/admin-profile.js
new file mode 100644
index 0000000..7abe65a
--- /dev/null
+++ b/Admin/profile/admin-profile.js
@@ -0,0 +1,220 @@
+const API_BASE_URL = "https://aidloop-backend.onrender.com/api";
+
+const els = {
+ adminAvatar: document.getElementById("adminAvatar"),
+ adminNameMini: document.getElementById("adminNameMini"),
+ adminRoleMini: document.getElementById("adminRoleMini"),
+ fullName: document.getElementById("fullName"),
+ emailAddress: document.getElementById("emailAddress"),
+ role: document.getElementById("role"),
+ phoneNumber: document.getElementById("phoneNumber"),
+ editProfileBtn: document.getElementById("editProfileBtn"),
+ profileFeedback: document.getElementById("profileFeedback"),
+ currentPassword: document.getElementById("currentPassword"),
+ newPassword: document.getElementById("newPassword"),
+ confirmPassword: document.getElementById("confirmPassword"),
+ passwordForm: document.getElementById("passwordForm"),
+ passwordFeedback: document.getElementById("passwordFeedback"),
+ logoutBtn: document.getElementById("logoutBtn"),
+ logoutModal: document.getElementById("logoutModal"),
+ closeLogoutModal: document.getElementById("closeLogoutModal"),
+ cancelLogout: document.getElementById("cancelLogout"),
+ confirmLogout: document.getElementById("confirmLogout")
+};
+
+let profileEditMode = false;
+let currentAdmin = null;
+
+async function apiRequest(endpoint, options = {}) {
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ ...(options.headers || {})
+ },
+ ...options
+ });
+
+ const contentType = response.headers.get("content-type") || "";
+ const data = contentType.includes("application/json")
+ ? await response.json()
+ : await response.text();
+
+ if (!response.ok) {
+ throw new Error(
+ (data && data.message) ||
+ (data && data.error) ||
+ "Request failed"
+ );
+ }
+
+ return data;
+}
+
+function setFeedback(element, message, type = "") {
+ element.textContent = message;
+ element.className = "feedback";
+ if (type) {
+ element.classList.add(type);
+ }
+}
+
+function fillProfile(profile) {
+ currentAdmin = profile;
+
+ const fullName = profile.fullName || profile.name || "Admin User";
+ const role = profile.role
+ ? profile.role.charAt(0).toUpperCase() + profile.role.slice(1)
+ : "Admin";
+
+ els.adminNameMini.textContent = fullName;
+ els.adminRoleMini.textContent = role;
+
+ els.fullName.value = fullName;
+ els.emailAddress.value = profile.email || "";
+ els.role.value = role;
+ els.phoneNumber.value = profile.phoneNumber || profile.phone || "";
+
+ if (profile.profileImage) {
+ els.adminAvatar.src = profile.profileImage;
+ }
+}
+
+function setProfileInputsReadonly(readonly) {
+ els.phoneNumber.readOnly = readonly;
+}
+
+function toggleProfileEditMode(forceValue = null) {
+ profileEditMode = forceValue !== null ? forceValue : !profileEditMode;
+ setProfileInputsReadonly(!profileEditMode);
+ els.editProfileBtn.textContent = profileEditMode ? "Save Profile" : "Edit Profile";
+}
+
+async function loadAdminProfile() {
+ try {
+ let profile;
+ try {
+ profile = await apiRequest("/users/me");
+ } catch {
+ profile = await apiRequest("/user/me");
+ }
+
+ fillProfile(profile);
+ toggleProfileEditMode(false);
+ } catch (error) {
+ setFeedback(els.profileFeedback, error.message || "Failed to load profile.", "error");
+ }
+}
+
+async function saveProfile() {
+ try {
+ els.editProfileBtn.disabled = true;
+
+ const updated = await apiRequest("/user/me", {
+ method: "PUT",
+ body: JSON.stringify({
+ phoneNumber: els.phoneNumber.value.trim()
+ })
+ });
+
+ fillProfile({
+ ...currentAdmin,
+ ...updated,
+ phoneNumber: updated.phoneNumber || els.phoneNumber.value.trim()
+ });
+
+ toggleProfileEditMode(false);
+ setFeedback(els.profileFeedback, "Profile updated successfully.", "success");
+ } catch (error) {
+ setFeedback(els.profileFeedback, error.message || "Failed to update profile.", "error");
+ } finally {
+ els.editProfileBtn.disabled = false;
+ }
+}
+
+function handleEditProfile() {
+ setFeedback(els.profileFeedback, "");
+ if (!profileEditMode) {
+ toggleProfileEditMode(true);
+ return;
+ }
+ saveProfile();
+}
+
+function handlePasswordSubmit(event) {
+ event.preventDefault();
+
+ const currentPassword = els.currentPassword.value.trim();
+ const newPassword = els.newPassword.value.trim();
+ const confirmPassword = els.confirmPassword.value.trim();
+
+ if (!currentPassword || !newPassword || !confirmPassword) {
+ setFeedback(els.passwordFeedback, "All password fields are required.", "error");
+ return;
+ }
+
+ if (newPassword !== confirmPassword) {
+ setFeedback(els.passwordFeedback, "New passwords do not match.", "error");
+ return;
+ }
+
+ setFeedback(
+ els.passwordFeedback,
+ "No admin change-password endpoint has been provided yet.",
+ "error"
+ );
+}
+
+function openLogoutModal() {
+ els.logoutModal.classList.remove("hidden");
+}
+
+function closeLogoutModal() {
+ els.logoutModal.classList.add("hidden");
+ els.confirmLogout.disabled = false;
+ els.confirmLogout.textContent = "Yes, Log out";
+}
+
+async function handleLogout() {
+ try {
+ els.confirmLogout.disabled = true;
+ els.confirmLogout.textContent = "Logging out...";
+
+ await apiRequest("/auth/logout", {
+ method: "POST"
+ });
+ } catch (error) {
+ console.warn("Logout failed:", error.message);
+ } finally {
+ localStorage.clear();
+ sessionStorage.clear();
+ window.location.href = "../../index.html";
+ }
+}
+
+function bindUI() {
+ els.editProfileBtn.addEventListener("click", handleEditProfile);
+ els.passwordForm.addEventListener("submit", handlePasswordSubmit);
+
+ els.logoutBtn.addEventListener("click", openLogoutModal);
+ els.closeLogoutModal.addEventListener("click", closeLogoutModal);
+ els.cancelLogout.addEventListener("click", closeLogoutModal);
+ els.confirmLogout.addEventListener("click", handleLogout);
+
+ els.logoutModal.addEventListener("click", (event) => {
+ if (event.target === els.logoutModal) {
+ closeLogoutModal();
+ }
+ });
+
+ document.addEventListener("keydown", (event) => {
+ if (event.key === "Escape" && !els.logoutModal.classList.contains("hidden")) {
+ closeLogoutModal();
+ }
+ });
+}
+
+document.addEventListener("DOMContentLoaded", async () => {
+ bindUI();
+ await loadAdminProfile();
+});
\ No newline at end of file
diff --git a/Admin/user/user-details.css b/Admin/user/user-details.css
new file mode 100644
index 0000000..502e744
--- /dev/null
+++ b/Admin/user/user-details.css
@@ -0,0 +1,201 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #edf2f6;
+ color: #172033;
+}
+
+.page {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1.5rem;
+}
+
+.details-card {
+ width: 100%;
+ max-width: 42rem;
+ background: #ffffff;
+ border-radius: 1rem;
+ padding: 1.8rem;
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.12);
+}
+
+.header-row {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 1rem;
+ margin-bottom: 1.5rem;
+}
+
+.header-row h1 {
+ font-size: 1.6rem;
+ font-weight: 700;
+ color: #172033;
+ line-height: 1.3;
+}
+
+.role-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 6rem;
+ padding: 0.45rem 0.85rem;
+ border-radius: 0.55rem;
+ color: #ffffff;
+ font-size: 0.72rem;
+ font-weight: 700;
+ text-transform: capitalize;
+}
+
+.role-badge.organizer {
+ background: #1d7de2;
+}
+
+.role-badge.volunteer {
+ background: #16a34a;
+}
+
+.role-badge.admin {
+ background: #6b7280;
+}
+
+.role-badge.user {
+ background: #64748b;
+}
+
+.info-grid {
+ display: grid;
+ grid-template-columns: 8rem 1fr;
+ gap: 0.9rem 1rem;
+ margin-bottom: 1.5rem;
+}
+
+.label {
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: #223f6b;
+}
+
+.value {
+ font-size: 0.9rem;
+ color: #374151;
+ word-break: break-word;
+}
+
+.status-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 6rem;
+ padding: 0.4rem 0.8rem;
+ border-radius: 0.5rem;
+ color: #ffffff;
+ font-size: 0.72rem;
+ font-weight: 700;
+}
+
+.status-badge.active {
+ background: #16a34a;
+}
+
+.status-badge.deactivated {
+ background: #dc2626;
+}
+
+.description-section h2 {
+ font-size: 1rem;
+ font-weight: 700;
+ margin-bottom: 0.7rem;
+ color: #172033;
+}
+
+.description-box {
+ background: #f8fafc;
+ border: 0.0625rem solid #e5e7eb;
+ border-radius: 0.75rem;
+ padding: 1rem;
+ font-size: 0.9rem;
+ line-height: 1.7;
+ color: #374151;
+}
+
+.feedback {
+ min-height: 1.2rem;
+ margin-top: 1rem;
+ text-align: center;
+ font-size: 0.85rem;
+}
+
+.feedback.success {
+ color: #16a34a;
+}
+
+.feedback.error {
+ color: #dc2626;
+}
+
+.action-row {
+ margin-top: 1.3rem;
+ display: flex;
+ justify-content: flex-end;
+ gap: 0.8rem;
+}
+
+.action-btn {
+ border: none;
+ border-radius: 0.55rem;
+ padding: 0.85rem 1.2rem;
+ font-size: 0.9rem;
+ cursor: pointer;
+}
+
+.deactivate-btn {
+ background: #dc2626;
+ color: #ffffff;
+}
+
+.reactivate-btn {
+ background: #e5e7eb;
+ color: #172033;
+}
+
+.action-btn:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+}
+
+@media (max-width: 36rem) {
+ .details-card {
+ padding: 1.2rem;
+ }
+
+ .header-row {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .info-grid {
+ grid-template-columns: 1fr;
+ gap: 0.4rem;
+ }
+
+ .label {
+ margin-top: 0.4rem;
+ }
+
+ .action-row {
+ flex-direction: column;
+ }
+
+ .action-btn {
+ width: 100%;
+ }
+}
\ No newline at end of file
diff --git a/Admin/user/user-details.html b/Admin/user/user-details.html
new file mode 100644
index 0000000..827d916
--- /dev/null
+++ b/Admin/user/user-details.html
@@ -0,0 +1,61 @@
+
+
+
+
+
+ User Details
+
+
+
+
+
+
+
+
+
+
+
+
+
Name:
+
Loading...
+
+
Email:
+
—
+
+
Phone No:
+
—
+
+
Location:
+
—
+
+
Date Joined:
+
—
+
+
Status
+
+ Loading...
+
+
+
+
+
Description
+
+ Loading description...
+
+
+
+
+
+
+ Deactivate
+ Reactivate
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Admin/user/user-details.js b/Admin/user/user-details.js
new file mode 100644
index 0000000..02219db
--- /dev/null
+++ b/Admin/user/user-details.js
@@ -0,0 +1,214 @@
+const API_BASE_URL = "https://aidloop-backend.onrender.com/api";
+
+const els = {
+ userTitle: document.getElementById("userTitle"),
+ roleBadge: document.getElementById("roleBadge"),
+ userName: document.getElementById("userName"),
+ email: document.getElementById("email"),
+ phoneNumber: document.getElementById("phoneNumber"),
+ location: document.getElementById("location"),
+ dateJoined: document.getElementById("dateJoined"),
+ statusBadge: document.getElementById("statusBadge"),
+ description: document.getElementById("description"),
+ feedback: document.getElementById("feedback"),
+ deactivateBtn: document.getElementById("deactivateBtn"),
+ reactivateBtn: document.getElementById("reactivateBtn")
+};
+
+const userId = new URLSearchParams(window.location.search).get("id");
+let currentUser = null;
+
+async function apiRequest(endpoint, options = {}) {
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ ...(options.headers || {})
+ },
+ ...options
+ });
+
+ const contentType = response.headers.get("content-type") || "";
+ const data = contentType.includes("application/json")
+ ? await response.json()
+ : await response.text();
+
+ if (!response.ok) {
+ throw new Error(
+ (data && data.message) ||
+ (data && data.error) ||
+ "Request failed"
+ );
+ }
+
+ return data;
+}
+
+function normalizeUsers(payload) {
+ if (Array.isArray(payload)) return payload;
+ if (Array.isArray(payload?.users)) return payload.users;
+ if (Array.isArray(payload?.data)) return payload.data;
+ return [];
+}
+
+function formatDate(dateValue) {
+ if (!dateValue) return "—";
+ const date = new Date(dateValue);
+ if (Number.isNaN(date.getTime())) return dateValue;
+ return date.toLocaleDateString("en-GB", {
+ day: "2-digit",
+ month: "long",
+ year: "numeric"
+ });
+}
+
+function getDisplayName(user) {
+ return user.fullName || user.name || user.organizationName || "User";
+}
+
+function getLocation(user) {
+ if (typeof user.location === "string" && user.location.trim()) {
+ return user.location;
+ }
+
+ if (user.location && typeof user.location === "object") {
+ return (
+ [user.location.venue, user.location.city || user.location.state]
+ .filter(Boolean)
+ .join(", ") || "—"
+ );
+ }
+
+ return user.city || user.state || "—";
+}
+
+function getRole(user) {
+ return String(user.role || "user").toLowerCase();
+}
+
+function getStatus(user) {
+ return user.isActive === false ? "deactivated" : "active";
+}
+
+function setFeedback(message, type = "") {
+ els.feedback.textContent = message;
+ els.feedback.className = "feedback";
+ if (type) {
+ els.feedback.classList.add(type);
+ }
+}
+
+function setRoleBadge(role) {
+ els.roleBadge.textContent = role.charAt(0).toUpperCase() + role.slice(1);
+ els.roleBadge.className = "role-badge";
+ els.roleBadge.classList.add(role);
+}
+
+function setStatusBadge(status) {
+ els.statusBadge.textContent =
+ status === "deactivated" ? "Deactivated" : "Active";
+ els.statusBadge.className = "status-badge";
+ els.statusBadge.classList.add(status);
+}
+
+function syncButtons(status) {
+ if (status === "deactivated") {
+ els.deactivateBtn.disabled = true;
+ els.deactivateBtn.textContent = "Deactivated";
+ els.reactivateBtn.disabled = false;
+ } else {
+ els.deactivateBtn.disabled = false;
+ els.deactivateBtn.textContent = "Deactivate";
+ els.reactivateBtn.disabled = false;
+ }
+}
+
+function populateUser(user) {
+ currentUser = user;
+
+ const role = getRole(user);
+ const status = getStatus(user);
+
+ els.userTitle.textContent = getDisplayName(user);
+ els.userName.textContent = getDisplayName(user);
+ els.email.textContent = user.email || "—";
+ els.phoneNumber.textContent = user.phoneNumber || user.phone || "—";
+ els.location.textContent = getLocation(user);
+ els.dateJoined.textContent = formatDate(user.createdAt || user.dateJoined);
+ els.description.textContent =
+ user.description ||
+ user.bio ||
+ "No description available.";
+
+ setRoleBadge(role);
+ setStatusBadge(status);
+ syncButtons(status);
+}
+
+async function loadUserDetails() {
+ if (!userId) {
+ els.userTitle.textContent = "No user selected";
+ els.description.textContent = "No user ID provided.";
+ els.deactivateBtn.disabled = true;
+ els.reactivateBtn.disabled = true;
+ return;
+ }
+
+ try {
+ const payload = await apiRequest("/user").catch(() => apiRequest("/users"));
+ const users = normalizeUsers(payload);
+
+ const user = users.find(
+ (item) => String(item._id || item.id) === String(userId)
+ );
+
+ if (!user) {
+ throw new Error("User not found");
+ }
+
+ populateUser(user);
+ } catch (error) {
+ els.userTitle.textContent = "Unable to load user";
+ els.description.textContent = error.message || "Failed to fetch user details.";
+ els.deactivateBtn.disabled = true;
+ els.reactivateBtn.disabled = true;
+ setFeedback(error.message || "Failed to load user details.", "error");
+ }
+}
+
+async function deactivateUser() {
+ if (!userId) return;
+
+ try {
+ els.deactivateBtn.disabled = true;
+ els.deactivateBtn.textContent = "Deactivating...";
+
+ await apiRequest(`/admin/users/${userId}/deactivate`, {
+ method: "PATCH"
+ });
+
+ currentUser = {
+ ...currentUser,
+ isActive: false
+ };
+
+ setStatusBadge("deactivated");
+ syncButtons("deactivated");
+ setFeedback("User deactivated successfully.", "success");
+ } catch (error) {
+ syncButtons(getStatus(currentUser || {}));
+ setFeedback(error.message || "Failed to deactivate user.", "error");
+ }
+}
+
+function handleReactivate() {
+ setFeedback(
+ "No reactivation endpoint has been provided yet.",
+ "error"
+ );
+}
+
+els.deactivateBtn.addEventListener("click", deactivateUser);
+els.reactivateBtn.addEventListener("click", handleReactivate);
+
+document.addEventListener("DOMContentLoaded", loadUserDetails);
\ No newline at end of file
diff --git a/Admin/user/user-management.css b/Admin/user/user-management.css
new file mode 100644
index 0000000..bbdd83e
--- /dev/null
+++ b/Admin/user/user-management.css
@@ -0,0 +1,434 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #edf2f6;
+ color: #172033;
+}
+
+.dashboard {
+ display: flex;
+ min-height: 100vh;
+}
+
+.sidebar {
+ width: 15rem;
+ background: #223f6b;
+ color: #ffffff;
+ padding: 1.8rem 0.9rem;
+}
+
+.sidebar-logo {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.35rem;
+ margin-bottom: 2.4rem;
+}
+
+.sidebar-logo img {
+ width: 3.1rem;
+ height: 3.1rem;
+ object-fit: contain;
+}
+
+.sidebar-menu {
+ list-style: none;
+}
+
+.sidebar-menu li {
+ margin-bottom: 0.8rem;
+}
+
+.sidebar-menu li a,
+.logout-btn {
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+ text-decoration: none;
+ color: #ffffff;
+ padding: 0.9rem 0.85rem;
+ border-radius: 0.45rem;
+ font-size: 0.95rem;
+ transition: background 0.2s ease;
+ width: 100%;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+}
+
+.sidebar-menu li a i,
+.logout-btn i {
+ width: 1rem;
+ text-align: center;
+}
+
+.sidebar-menu li.active a,
+.sidebar-menu li a:hover {
+ background: #1d7de2;
+}
+
+.logout-btn:hover {
+ background: #ef2f2f;
+}
+
+.main-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.topbar {
+ height: 5.8rem;
+ background: #ffffff;
+ border-bottom: 0.0625rem solid #e6e8eb;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 1.8rem;
+}
+
+.search-box {
+ width: 21rem;
+ height: 2.9rem;
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ border: 0.0625rem solid #d9dde3;
+ border-radius: 0.35rem;
+ padding: 0 0.9rem;
+ background: #ffffff;
+}
+
+.search-box i {
+ color: #6b7280;
+ font-size: 0.95rem;
+}
+
+.search-box input {
+ width: 100%;
+ border: none;
+ outline: none;
+ background: transparent;
+ font-size: 0.95rem;
+ color: #374151;
+}
+
+.topbar-right {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.notification {
+ position: relative;
+ color: #111827;
+ font-size: 1.2rem;
+}
+
+.notification-dot {
+ position: absolute;
+ top: 0.08rem;
+ right: -0.12rem;
+ width: 0.45rem;
+ height: 0.45rem;
+ background: #ef2f2f;
+ border-radius: 50%;
+}
+
+.admin-profile {
+ display: flex;
+ align-items: center;
+ gap: 0.7rem;
+}
+
+.user-avatar {
+ width: 2.45rem;
+ height: 2.45rem;
+ border-radius: 50%;
+ object-fit: cover;
+}
+
+.admin-text h4 {
+ font-size: 0.82rem;
+ font-weight: 600;
+ color: #172033;
+ line-height: 1.2;
+}
+
+.admin-text p {
+ font-size: 0.72rem;
+ color: #6b7280;
+ line-height: 1.2;
+}
+
+.page-content {
+ padding: 4rem 1.4rem 2rem;
+}
+
+.page-header {
+ margin-bottom: 2rem;
+}
+
+.page-header h1 {
+ font-size: 2.1rem;
+ font-weight: 700;
+ margin-bottom: 0.3rem;
+}
+
+.page-header p {
+ font-size: 0.78rem;
+ color: #6b7280;
+}
+
+.table-wrapper {
+ background: #ffffff;
+ overflow: hidden;
+ border-radius: 0.6rem;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+thead {
+ background: #f4f6f8;
+}
+
+th,
+td {
+ text-align: left;
+ padding: 1rem 1.4rem;
+}
+
+th {
+ font-size: 0.62rem;
+ letter-spacing: 0.08em;
+ color: #7b8492;
+ font-weight: 700;
+}
+
+td {
+ font-size: 0.72rem;
+ color: #172033;
+ border-top: 0.0625rem solid #eef1f3;
+}
+
+.role-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 5.5rem;
+ padding: 0.35rem 0.7rem;
+ border-radius: 0.45rem;
+ color: #ffffff;
+ font-size: 0.58rem;
+ font-weight: 700;
+ text-transform: capitalize;
+}
+
+.role-badge.organizer {
+ background: #1d7de2;
+}
+
+.role-badge.volunteer {
+ background: #16a34a;
+}
+
+.role-badge.admin {
+ background: #6b7280;
+}
+
+.actions-cell {
+ display: flex;
+ gap: 0.6rem;
+ flex-wrap: wrap;
+}
+
+.action-link,
+.action-btn {
+ color: #1d7de2;
+ text-decoration: none;
+ font-weight: 600;
+ font-size: 0.72rem;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+}
+
+.action-link:hover,
+.action-btn:hover {
+ text-decoration: underline;
+}
+
+.action-btn.deactivated {
+ color: #9ca3af;
+ cursor: not-allowed;
+ text-decoration: none;
+}
+
+.empty-state {
+ text-align: center;
+ padding: 3rem 1rem;
+}
+
+.empty-icon {
+ width: 4rem;
+ height: 4rem;
+ margin: 0 auto 1rem;
+ border-radius: 50%;
+ background: #e8f0fb;
+ color: #1d7de2;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.4rem;
+}
+
+.empty-state h2 {
+ font-size: 1.1rem;
+ margin-bottom: 0.4rem;
+}
+
+.empty-state p {
+ color: #6b7280;
+ font-size: 0.85rem;
+}
+
+.hidden {
+ display: none !important;
+}
+
+.modal-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(15, 23, 42, 0.45);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1rem;
+ z-index: 999;
+}
+
+.modal-card {
+ width: 100%;
+ max-width: 25rem;
+ background: #ffffff;
+ border-radius: 1rem;
+ padding: 1.5rem;
+ position: relative;
+ text-align: center;
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.15);
+}
+
+.modal-close-btn {
+ position: absolute;
+ top: 0.7rem;
+ right: 0.9rem;
+ border: none;
+ background: transparent;
+ font-size: 1.5rem;
+ color: #6b7280;
+ cursor: pointer;
+}
+
+.modal-icon-wrap {
+ width: 3.5rem;
+ height: 3.5rem;
+ margin: 0 auto 1rem;
+ border-radius: 50%;
+ background: #fee2e2;
+ color: #dc2626;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.25rem;
+}
+
+.modal-card h3 {
+ font-size: 1.2rem;
+ margin-bottom: 0.5rem;
+ color: #172033;
+}
+
+.modal-card p {
+ font-size: 0.92rem;
+ color: #6b7280;
+ margin-bottom: 1.3rem;
+}
+
+.modal-actions {
+ display: flex;
+ gap: 0.8rem;
+ justify-content: center;
+}
+
+.modal-btn {
+ border: none;
+ border-radius: 0.55rem;
+ padding: 0.8rem 1rem;
+ font-size: 0.9rem;
+ cursor: pointer;
+ min-width: 8rem;
+}
+
+.modal-btn.secondary {
+ background: #e5e7eb;
+ color: #172033;
+}
+
+.modal-btn.danger {
+ background: #dc2626;
+ color: #ffffff;
+}
+
+@media (max-width: 75rem) {
+ .table-wrapper {
+ overflow-x: auto;
+ }
+
+ table {
+ min-width: 48rem;
+ }
+}
+
+@media (max-width: 62rem) {
+ .sidebar {
+ display: none;
+ }
+
+ .topbar {
+ padding: 0 1rem;
+ }
+
+ .search-box {
+ width: 16rem;
+ }
+
+ .page-content {
+ padding: 2rem 1rem;
+ }
+
+ .admin-text {
+ display: none;
+ }
+}
+
+@media (max-width: 36rem) {
+ .search-box {
+ width: 12rem;
+ }
+
+ .modal-actions {
+ flex-direction: column;
+ }
+
+ .modal-btn {
+ width: 100%;
+ }
+}
\ No newline at end of file
diff --git a/Admin/user/user-management.html b/Admin/user/user-management.html
new file mode 100644
index 0000000..833bd89
--- /dev/null
+++ b/Admin/user/user-management.html
@@ -0,0 +1,141 @@
+
+
+
+
+
+ AIDLoop - User Management
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ USER NAME
+ USER EMAIL
+ LOCATION
+ ROLE
+ ACTIONS
+
+
+
+
+
+ Loading users...
+
+
+
+
+
+
+
+
+
No Users Found
+
No users match your current search.
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
Log out?
+
You are about to end your current admin session.
+
+
+ Cancel
+ Yes, Log out
+
+
+
+
+
+
+
diff --git a/Admin/user/user-management.js b/Admin/user/user-management.js
new file mode 100644
index 0000000..0336af8
--- /dev/null
+++ b/Admin/user/user-management.js
@@ -0,0 +1,255 @@
+const API_BASE_URL = "https://aidloop-backend.onrender.com/api";
+
+const els = {
+ adminName: document.getElementById("adminName"),
+ adminRole: document.getElementById("adminRole"),
+ adminAvatar: document.getElementById("adminAvatar"),
+ userTable: document.getElementById("userTable"),
+ userTableWrap: document.getElementById("userTableWrap"),
+ emptyState: document.getElementById("emptyState"),
+ searchInput: document.getElementById("searchInput"),
+ logoutBtn: document.getElementById("logoutBtn"),
+ logoutModal: document.getElementById("logoutModal"),
+ closeLogoutModal: document.getElementById("closeLogoutModal"),
+ cancelLogout: document.getElementById("cancelLogout"),
+ confirmLogout: document.getElementById("confirmLogout")
+};
+
+let usersCache = [];
+
+async function apiRequest(endpoint, options = {}) {
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ ...(options.headers || {})
+ },
+ ...options
+ });
+
+ const contentType = response.headers.get("content-type") || "";
+ const data = contentType.includes("application/json")
+ ? await response.json()
+ : await response.text();
+
+ if (!response.ok) {
+ throw new Error(
+ (data && data.message) ||
+ (data && data.error) ||
+ "Request failed"
+ );
+ }
+
+ return data;
+}
+
+function normalizeUsers(payload) {
+ if (Array.isArray(payload)) return payload;
+ if (Array.isArray(payload?.users)) return payload.users;
+ if (Array.isArray(payload?.data)) return payload.data;
+ return [];
+}
+
+function getDisplayName(user) {
+ return user.fullName || user.name || user.organizationName || "User";
+}
+
+function getLocation(user) {
+ if (typeof user.location === "string" && user.location.trim()) {
+ return user.location;
+ }
+
+ if (user.location && typeof user.location === "object") {
+ return (
+ [user.location.venue, user.location.city || user.location.state]
+ .filter(Boolean)
+ .join(", ") || "—"
+ );
+ }
+
+ return user.city || user.state || "—";
+}
+
+function getRole(user) {
+ return String(user.role || "user").toLowerCase();
+}
+
+function renderUsers() {
+ const query = els.searchInput.value.trim().toLowerCase();
+
+ const filtered = usersCache.filter((user) => {
+ const searchableText = `
+ ${getDisplayName(user)}
+ ${user.email || ""}
+ ${getLocation(user)}
+ ${getRole(user)}
+ `.toLowerCase();
+
+ return searchableText.includes(query);
+ });
+
+ if (!filtered.length) {
+ els.userTableWrap.style.display = "none";
+ els.emptyState.style.display = "block";
+ return;
+ }
+
+ els.userTableWrap.style.display = "table";
+ els.emptyState.style.display = "none";
+
+ els.userTable.innerHTML = filtered.map((user) => {
+ const role = getRole(user);
+ const id = user._id || user.id || "";
+ const isActive = user.isActive !== false;
+
+ return `
+
+ ${getDisplayName(user)}
+ ${user.email || "—"}
+ ${getLocation(user)}
+ ${role}
+
+
+
View
+
+ ${isActive ? "Deactivate" : "Deactivated"}
+
+
+
+
+ `;
+ }).join("");
+
+ bindDeactivateButtons();
+}
+
+function bindDeactivateButtons() {
+ document.querySelectorAll(".action-btn[data-id]").forEach((button) => {
+ button.addEventListener("click", async () => {
+ const id = button.dataset.id;
+ if (!id || button.disabled) return;
+
+ try {
+ button.disabled = true;
+ button.textContent = "Deactivating...";
+
+ await apiRequest(`/admin/users/${id}/deactivate`, {
+ method: "PATCH"
+ });
+
+ usersCache = usersCache.map((user) =>
+ String(user._id || user.id) === String(id)
+ ? { ...user, isActive: false }
+ : user
+ );
+
+ renderUsers();
+ } catch (error) {
+ console.error("Failed to deactivate user:", error.message);
+ button.disabled = false;
+ button.textContent = "Deactivate";
+ }
+ });
+ });
+}
+
+function openLogoutModal() {
+ els.logoutModal.classList.remove("hidden");
+}
+
+function closeLogoutModal() {
+ els.logoutModal.classList.add("hidden");
+ els.confirmLogout.disabled = false;
+ els.confirmLogout.textContent = "Yes, Log out";
+}
+
+async function handleLogout() {
+ try {
+ els.confirmLogout.disabled = true;
+ els.confirmLogout.textContent = "Logging out...";
+
+ await apiRequest("/auth/logout", {
+ method: "POST"
+ });
+ } catch (error) {
+ console.warn("Logout failed:", error.message);
+ } finally {
+ localStorage.clear();
+ sessionStorage.clear();
+ window.location.href = "../../index.html";
+ }
+}
+
+async function loadAdminProfile() {
+ try {
+ let profile;
+ try {
+ profile = await apiRequest("/users/me");
+ } catch {
+ profile = await apiRequest("/user/me");
+ }
+
+ els.adminName.textContent =
+ profile.fullName ||
+ profile.name ||
+ "Admin User";
+
+ els.adminRole.textContent =
+ profile.role
+ ? profile.role.charAt(0).toUpperCase() + profile.role.slice(1)
+ : "Admin";
+
+ if (profile.profileImage) {
+ els.adminAvatar.src = profile.profileImage;
+ }
+ } catch (error) {
+ console.error("Failed to load admin profile:", error.message);
+ window.location.href = "../profile/admin-profile.html";
+ }
+}
+
+async function loadUsers() {
+ try {
+ const payload = await apiRequest("/user").catch(() => apiRequest("/users"));
+ usersCache = normalizeUsers(payload);
+ renderUsers();
+ } catch (error) {
+ console.error("Failed to load users:", error.message);
+ els.userTable.innerHTML = `
+
+ Failed to load users.
+
+ `;
+ }
+}
+
+function bindUI() {
+ els.searchInput.addEventListener("input", renderUsers);
+
+ els.logoutBtn.addEventListener("click", openLogoutModal);
+ els.closeLogoutModal.addEventListener("click", closeLogoutModal);
+ els.cancelLogout.addEventListener("click", closeLogoutModal);
+ els.confirmLogout.addEventListener("click", handleLogout);
+
+ els.logoutModal.addEventListener("click", (event) => {
+ if (event.target === els.logoutModal) {
+ closeLogoutModal();
+ }
+ });
+
+ document.addEventListener("keydown", (event) => {
+ if (event.key === "Escape" && !els.logoutModal.classList.contains("hidden")) {
+ closeLogoutModal();
+ }
+ });
+}
+
+document.addEventListener("DOMContentLoaded", async () => {
+ bindUI();
+ await loadAdminProfile();
+ await loadUsers();
+});
\ No newline at end of file
diff --git a/Admin/verification/verification-details.css b/Admin/verification/verification-details.css
new file mode 100644
index 0000000..26d62ec
--- /dev/null
+++ b/Admin/verification/verification-details.css
@@ -0,0 +1,186 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #edf2f6;
+ color: #172033;
+}
+
+.page {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1.5rem;
+}
+
+.card {
+ width: 100%;
+ max-width: 42rem;
+ background: #ffffff;
+ border-radius: 1rem;
+ padding: 1.8rem;
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.12);
+}
+
+.card-header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 1rem;
+ margin-bottom: 1.5rem;
+}
+
+.card-header h1 {
+ font-size: 1.6rem;
+ font-weight: 700;
+ color: #172033;
+ line-height: 1.3;
+}
+
+.status-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 7rem;
+ padding: 0.45rem 0.85rem;
+ border-radius: 0.55rem;
+ color: #ffffff;
+ font-size: 0.72rem;
+ font-weight: 700;
+ text-transform: capitalize;
+}
+
+.status-badge.awaiting {
+ background: #f59e0b;
+}
+
+.status-badge.verified {
+ background: #16a34a;
+}
+
+.status-badge.rejected {
+ background: #dc2626;
+}
+
+.details-grid {
+ display: grid;
+ grid-template-columns: 8rem 1fr;
+ gap: 0.9rem 1rem;
+ margin-bottom: 1.5rem;
+}
+
+.label {
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: #223f6b;
+}
+
+.value {
+ font-size: 0.9rem;
+ color: #374151;
+ word-break: break-word;
+}
+
+.description-section h2 {
+ font-size: 1rem;
+ font-weight: 700;
+ margin-bottom: 0.7rem;
+ color: #172033;
+}
+
+.description-box {
+ background: #f8fafc;
+ border: 0.0625rem solid #e5e7eb;
+ border-radius: 0.75rem;
+ padding: 1rem;
+ font-size: 0.9rem;
+ line-height: 1.7;
+ color: #374151;
+}
+
+.actions {
+ margin-top: 1.3rem;
+ display: flex;
+ justify-content: flex-end;
+ gap: 0.8rem;
+}
+
+.btn {
+ border: none;
+ border-radius: 0.55rem;
+ padding: 0.85rem 1.2rem;
+ font-size: 0.9rem;
+ cursor: pointer;
+}
+
+.btn-reject {
+ background: #dc2626;
+ color: #ffffff;
+}
+
+.btn-approve {
+ background: #16a34a;
+ color: #ffffff;
+}
+
+.btn:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+}
+
+.feedback {
+ min-height: 1.2rem;
+ margin-top: 1rem;
+ text-align: center;
+ font-size: 0.85rem;
+}
+
+.feedback.success {
+ color: #16a34a;
+}
+
+.feedback.error {
+ color: #dc2626;
+}
+
+.social-link {
+ color: #1d7de2;
+ text-decoration: none;
+}
+
+.social-link:hover {
+ text-decoration: underline;
+}
+
+@media (max-width: 36rem) {
+ .card {
+ padding: 1.2rem;
+ }
+
+ .card-header {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .details-grid {
+ grid-template-columns: 1fr;
+ gap: 0.4rem;
+ }
+
+ .label {
+ margin-top: 0.4rem;
+ }
+
+ .actions {
+ flex-direction: column;
+ }
+
+ .btn {
+ width: 100%;
+ }
+}
\ No newline at end of file
diff --git a/Admin/verification/verification-details.html b/Admin/verification/verification-details.html
new file mode 100644
index 0000000..b7aff3a
--- /dev/null
+++ b/Admin/verification/verification-details.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+ Verification Details
+
+
+
+
+
+
+
+
+
+
+
+
+
Org. Name:
+
Loading...
+
+
Social Links:
+
—
+
+
Email:
+
—
+
+
Phone No:
+
—
+
+
Location:
+
—
+
+
+
+
Description
+
+
Loading organization description...
+
+
+
+
+ Reject
+ Approve
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Admin/verification/verification-details.js b/Admin/verification/verification-details.js
new file mode 100644
index 0000000..d7f74ed
--- /dev/null
+++ b/Admin/verification/verification-details.js
@@ -0,0 +1,248 @@
+const API_BASE_URL = "https://aidloop-backend.onrender.com/api";
+
+const els = {
+ orgTitle: document.getElementById("orgTitle"),
+ statusBadge: document.getElementById("statusBadge"),
+ orgName: document.getElementById("orgName"),
+ socialLinks: document.getElementById("socialLinks"),
+ email: document.getElementById("email"),
+ phoneNumber: document.getElementById("phoneNumber"),
+ location: document.getElementById("location"),
+ description: document.getElementById("description"),
+ rejectBtn: document.getElementById("rejectBtn"),
+ approveBtn: document.getElementById("approveBtn"),
+ feedback: document.getElementById("feedback")
+};
+
+const organizerId = new URLSearchParams(window.location.search).get("id");
+let currentOrganizer = null;
+
+async function apiRequest(endpoint, options = {}) {
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ ...(options.headers || {})
+ },
+ ...options
+ });
+
+ const contentType = response.headers.get("content-type") || "";
+ const data = contentType.includes("application/json")
+ ? await response.json()
+ : await response.text();
+
+ if (!response.ok) {
+ throw new Error(
+ (data && data.message) ||
+ (data && data.error) ||
+ "Request failed"
+ );
+ }
+
+ return data;
+}
+
+function normalizeUsers(payload) {
+ if (Array.isArray(payload)) return payload;
+ if (Array.isArray(payload?.users)) return payload.users;
+ if (Array.isArray(payload?.data)) return payload.data;
+ return [];
+}
+
+function getDisplayName(user) {
+ return user.fullName || user.name || user.organizationName || "Organization";
+}
+
+function getLocation(user) {
+ if (typeof user.location === "string" && user.location.trim()) {
+ return user.location;
+ }
+
+ if (user.location && typeof user.location === "object") {
+ return (
+ [user.location.venue, user.location.city || user.location.state]
+ .filter(Boolean)
+ .join(", ") || "—"
+ );
+ }
+
+ return user.city || user.state || "—";
+}
+
+function getVerificationStatus(user) {
+ const status = String(user.status || "").toLowerCase();
+ const approvalStatus = String(user.approvalStatus || "").toLowerCase();
+ const isVerified = Boolean(user.isVerified);
+
+ if (status === "rejected" || approvalStatus === "rejected") return "rejected";
+
+ if (
+ status === "verified" ||
+ status === "approved" ||
+ approvalStatus === "verified" ||
+ approvalStatus === "approved" ||
+ isVerified
+ ) {
+ return "verified";
+ }
+
+ return "awaiting";
+}
+
+function setStatusBadge(status) {
+ els.statusBadge.textContent =
+ status === "verified"
+ ? "Verified"
+ : status === "rejected"
+ ? "Rejected"
+ : "Awaiting Verification";
+
+ els.statusBadge.className = "status-badge";
+ els.statusBadge.classList.add(status);
+}
+
+function renderSocialLinks(user) {
+ const link =
+ user.website ||
+ user.socialLink ||
+ user.socialLinks?.[0] ||
+ "";
+
+ if (!link) {
+ els.socialLinks.textContent = "—";
+ return;
+ }
+
+ els.socialLinks.innerHTML = `
+
+ ${link}
+
+ `;
+}
+
+function setFeedback(message, type = "") {
+ els.feedback.textContent = message;
+ els.feedback.className = "feedback";
+ if (type) {
+ els.feedback.classList.add(type);
+ }
+}
+
+function populateOrganizer(user) {
+ const status = getVerificationStatus(user);
+
+ currentOrganizer = user;
+
+ els.orgTitle.textContent = getDisplayName(user);
+ els.orgName.textContent = getDisplayName(user);
+ els.email.textContent = user.email || "—";
+ els.phoneNumber.textContent =
+ user.phoneNumber || user.phone || "—";
+ els.location.textContent = getLocation(user);
+ els.description.textContent =
+ user.description ||
+ user.bio ||
+ "No organization description available.";
+
+ renderSocialLinks(user);
+ setStatusBadge(status);
+
+ if (status === "verified") {
+ els.approveBtn.disabled = true;
+ }
+
+ if (status === "rejected") {
+ els.rejectBtn.disabled = true;
+ }
+}
+
+async function loadOrganizerDetails() {
+ if (!organizerId) {
+ setFeedback("No organizer ID provided.", "error");
+ els.rejectBtn.disabled = true;
+ els.approveBtn.disabled = true;
+ return;
+ }
+
+ try {
+ let payload;
+
+ try {
+ payload = await apiRequest("/user");
+ } catch {
+ payload = await apiRequest("/users");
+ }
+
+ const users = normalizeUsers(payload);
+
+ const organizer = users.find(
+ (user) => String(user._id || user.id) === String(organizerId)
+ );
+
+ if (!organizer) {
+ throw new Error("Organizer not found");
+ }
+
+ populateOrganizer(organizer);
+ } catch (error) {
+ els.orgTitle.textContent = "Unable to load organizer";
+ els.description.textContent = "Failed to fetch organizer details.";
+ setFeedback(error.message || "Failed to load organizer details.", "error");
+ els.rejectBtn.disabled = true;
+ els.approveBtn.disabled = true;
+ }
+}
+
+async function approveOrganizer() {
+ if (!organizerId) return;
+
+ try {
+ els.approveBtn.disabled = true;
+ els.rejectBtn.disabled = true;
+
+ await apiRequest(`/admin/organizers/${organizerId}/approve`, {
+ method: "PATCH"
+ });
+
+ setStatusBadge("verified");
+ setFeedback("Organizer approved successfully.", "success");
+
+ setTimeout(() => {
+ window.location.href = "verification-queue.html";
+ }, 900);
+ } catch (error) {
+ els.approveBtn.disabled = false;
+ els.rejectBtn.disabled = false;
+ setFeedback(error.message || "Failed to approve organizer.", "error");
+ }
+}
+
+async function rejectOrganizer() {
+ if (!organizerId) return;
+
+ try {
+ els.approveBtn.disabled = true;
+ els.rejectBtn.disabled = true;
+
+ await apiRequest(`/admin/organizers/${organizerId}/reject`, {
+ method: "PATCH"
+ });
+
+ setStatusBadge("rejected");
+ setFeedback("Organizer rejected successfully.", "success");
+
+ setTimeout(() => {
+ window.location.href = "verification-queue.html";
+ }, 900);
+ } catch (error) {
+ els.approveBtn.disabled = false;
+ els.rejectBtn.disabled = false;
+ setFeedback(error.message || "Failed to reject organizer.", "error");
+ }
+}
+
+els.approveBtn.addEventListener("click", approveOrganizer);
+els.rejectBtn.addEventListener("click", rejectOrganizer);
+
+document.addEventListener("DOMContentLoaded", loadOrganizerDetails);
diff --git a/Admin/verification/verification-queue.css b/Admin/verification/verification-queue.css
new file mode 100644
index 0000000..6b24e05
--- /dev/null
+++ b/Admin/verification/verification-queue.css
@@ -0,0 +1,433 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #edf2f6;
+ color: #172033;
+}
+
+.dashboard {
+ display: flex;
+ min-height: 100vh;
+}
+
+.sidebar {
+ width: 15rem;
+ background: #223f6b;
+ color: #ffffff;
+ padding: 1.8rem 0.9rem;
+}
+
+.sidebar-logo {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.35rem;
+ margin-bottom: 2.4rem;
+}
+
+.sidebar-logo img {
+ width: 3.1rem;
+ height: 3.1rem;
+ object-fit: contain;
+}
+
+.sidebar-menu {
+ list-style: none;
+}
+
+.sidebar-menu li {
+ margin-bottom: 0.8rem;
+}
+
+.sidebar-menu li a,
+.logout-btn {
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+ text-decoration: none;
+ color: #ffffff;
+ padding: 0.9rem 0.85rem;
+ border-radius: 0.45rem;
+ font-size: 0.95rem;
+ transition: background 0.2s ease;
+ width: 100%;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+}
+
+.sidebar-menu li a i,
+.logout-btn i {
+ width: 1rem;
+ text-align: center;
+}
+
+.sidebar-menu li.active a,
+.sidebar-menu li a:hover {
+ background: #1d7de2;
+}
+
+.logout-btn:hover {
+ background: #ef2f2f;
+}
+
+.content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.topbar {
+ height: 5.8rem;
+ background: #ffffff;
+ border-bottom: 0.0625rem solid #e6e8eb;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 1.8rem;
+}
+
+.search {
+ width: 21rem;
+ height: 2.9rem;
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ border: 0.0625rem solid #d9dde3;
+ border-radius: 0.35rem;
+ padding: 0 0.9rem;
+ background: #ffffff;
+}
+
+.search i {
+ color: #6b7280;
+ font-size: 0.95rem;
+}
+
+.search input {
+ width: 100%;
+ border: none;
+ outline: none;
+ background: transparent;
+ font-size: 0.95rem;
+ color: #374151;
+}
+
+.top-icons {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ color: #111827;
+}
+
+.admin-mini-profile {
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+}
+
+.admin-mini-profile img {
+ width: 2.3rem;
+ height: 2.3rem;
+ border-radius: 50%;
+ object-fit: cover;
+}
+
+.admin-mini-profile span {
+ font-size: 0.85rem;
+ font-weight: 600;
+}
+
+.page-header {
+ padding: 2rem 1.4rem 1rem;
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 1rem;
+ flex-wrap: wrap;
+}
+
+.page-header h1 {
+ font-size: 2rem;
+ font-weight: 700;
+ margin-bottom: 0.3rem;
+}
+
+.page-header h3 {
+ font-size: 1rem;
+ margin-bottom: 0.3rem;
+}
+
+.page-header p {
+ font-size: 0.82rem;
+ color: #6b7280;
+}
+
+.filter-buttons {
+ display: flex;
+ gap: 0.8rem;
+ flex-wrap: wrap;
+}
+
+.filter-btn {
+ border: 0.0625rem solid #d9dde3;
+ background: #ffffff;
+ color: #374151;
+ padding: 0.75rem 1rem;
+ border-radius: 0.4rem;
+ font-size: 0.8rem;
+ cursor: pointer;
+}
+
+.filter-btn.active {
+ background: #1d7de2;
+ color: #ffffff;
+ border-color: #1d7de2;
+}
+
+.table-card {
+ margin: 0 1.4rem 2rem;
+ background: #ffffff;
+ border-radius: 0.6rem;
+ overflow: hidden;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+thead {
+ background: #f4f6f8;
+}
+
+th,
+td {
+ text-align: left;
+ padding: 1rem 1.4rem;
+}
+
+th {
+ font-size: 0.62rem;
+ letter-spacing: 0.08em;
+ color: #7b8492;
+ font-weight: 700;
+}
+
+td {
+ font-size: 0.72rem;
+ color: #172033;
+ border-top: 0.0625rem solid #eef1f3;
+}
+
+.badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 7rem;
+ padding: 0.35rem 0.7rem;
+ border-radius: 0.45rem;
+ color: #ffffff;
+ font-size: 0.62rem;
+ font-weight: 700;
+}
+
+.badge.awaiting {
+ background: #f59e0b;
+}
+
+.badge.verified {
+ background: #16a34a;
+}
+
+.badge.approved {
+ background: #16a34a;
+}
+
+.badge.rejected {
+ background: #dc2626;
+}
+
+.view {
+ border: none;
+ background: #1d7de2;
+ color: #ffffff;
+ padding: 0.55rem 0.9rem;
+ border-radius: 0.35rem;
+ font-size: 0.72rem;
+ cursor: pointer;
+}
+
+.view:hover {
+ opacity: 0.94;
+}
+
+.empty-state {
+ text-align: center;
+ padding: 3rem 1rem;
+}
+
+.empty-icon {
+ width: 4rem;
+ height: 4rem;
+ margin: 0 auto 1rem;
+ border-radius: 50%;
+ background: #e8f0fb;
+ color: #1d7de2;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.4rem;
+}
+
+.empty-state h2 {
+ font-size: 1.1rem;
+ margin-bottom: 0.4rem;
+}
+
+.empty-state p {
+ color: #6b7280;
+ font-size: 0.85rem;
+}
+
+.hidden {
+ display: none !important;
+}
+
+.modal-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(15, 23, 42, 0.45);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1rem;
+ z-index: 999;
+}
+
+.modal-card {
+ width: 100%;
+ max-width: 25rem;
+ background: #ffffff;
+ border-radius: 1rem;
+ padding: 1.5rem;
+ position: relative;
+ text-align: center;
+ box-shadow: 0 1rem 2.5rem rgba(0, 0, 0, 0.15);
+}
+
+.modal-close-btn {
+ position: absolute;
+ top: 0.7rem;
+ right: 0.9rem;
+ border: none;
+ background: transparent;
+ font-size: 1.5rem;
+ color: #6b7280;
+ cursor: pointer;
+}
+
+.modal-icon-wrap {
+ width: 3.5rem;
+ height: 3.5rem;
+ margin: 0 auto 1rem;
+ border-radius: 50%;
+ background: #fee2e2;
+ color: #dc2626;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.25rem;
+}
+
+.modal-card h3 {
+ font-size: 1.2rem;
+ margin-bottom: 0.5rem;
+ color: #172033;
+}
+
+.modal-card p {
+ font-size: 0.92rem;
+ color: #6b7280;
+ margin-bottom: 1.3rem;
+}
+
+.modal-actions {
+ display: flex;
+ gap: 0.8rem;
+ justify-content: center;
+}
+
+.modal-btn {
+ border: none;
+ border-radius: 0.55rem;
+ padding: 0.8rem 1rem;
+ font-size: 0.9rem;
+ cursor: pointer;
+ min-width: 8rem;
+}
+
+.modal-btn.secondary {
+ background: #e5e7eb;
+ color: #172033;
+}
+
+.modal-btn.danger {
+ background: #dc2626;
+ color: #ffffff;
+}
+
+@media (max-width: 75rem) {
+ .table-card {
+ overflow-x: auto;
+ }
+
+ table {
+ min-width: 46rem;
+ }
+}
+
+@media (max-width: 62rem) {
+ .sidebar {
+ display: none;
+ }
+
+ .topbar {
+ padding: 0 1rem;
+ }
+
+ .search {
+ width: 16rem;
+ }
+
+ .page-header {
+ padding: 2rem 1rem 1rem;
+ }
+
+ .table-card {
+ margin: 0 1rem 2rem;
+ }
+
+ .admin-mini-profile span {
+ display: none;
+ }
+}
+
+@media (max-width: 36rem) {
+ .search {
+ width: 12rem;
+ }
+
+ .modal-actions {
+ flex-direction: column;
+ }
+
+ .modal-btn {
+ width: 100%;
+ }
+}
diff --git a/Admin/verification/verification-queue.html b/Admin/verification/verification-queue.html
new file mode 100644
index 0000000..6da050d
--- /dev/null
+++ b/Admin/verification/verification-queue.html
@@ -0,0 +1,137 @@
+
+
+
+
+
+ AIDLoop Verification
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Loading...
+
+
+
+
+
+
+
+
+
+
+ Organization Name
+ Contact Email
+ Location
+ Status
+ Actions
+
+
+
+
+
+ Loading verification queue...
+
+
+
+
+
+
+
+
+
No organizations found
+
No organizations match the current filter.
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
Log out?
+
You are about to end your current admin session.
+
+
+ Cancel
+ Yes, Log out
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Admin/verification/verification-queue.js b/Admin/verification/verification-queue.js
new file mode 100644
index 0000000..cdb9a62
--- /dev/null
+++ b/Admin/verification/verification-queue.js
@@ -0,0 +1,601 @@
+// const API_BASE_URL = "https://aidloop-backend.onrender.com/api";
+
+// const els = {
+// searchInput: document.getElementById("searchInput"),
+// orgTable: document.getElementById("orgTable"),
+// orgTableWrap: document.getElementById("orgTableWrap"),
+// pendingCount: document.getElementById("pendingCount"),
+// adminName: document.getElementById("adminName"),
+// adminAvatar: document.getElementById("adminAvatar"),
+// filterButtons: document.querySelectorAll(".filter-btn"),
+// emptyState: document.getElementById("emptyState"),
+// logoutBtn: document.getElementById("logoutBtn"),
+// logoutModal: document.getElementById("logoutModal"),
+// closeLogoutModal: document.getElementById("closeLogoutModal"),
+// cancelLogout: document.getElementById("cancelLogout"),
+// confirmLogout: document.getElementById("confirmLogout")
+// };
+
+// let organizers = [];
+// let currentFilter = "awaiting";
+
+// async function apiRequest(endpoint, options = {}) {
+// const response = await fetch(`${API_BASE_URL}${endpoint}`, {
+// credentials: "include",
+// headers: {
+// "Content-Type": "application/json",
+// ...(options.headers || {})
+// },
+// ...options
+// });
+
+// const contentType = response.headers.get("content-type") || "";
+// const data = contentType.includes("application/json")
+// ? await response.json()
+// : await response.text();
+
+// if (!response.ok) {
+// throw new Error(
+// (data && data.message) ||
+// (data && data.error) ||
+// "Request failed"
+// );
+// }
+
+// return data;
+// }
+
+// function normalizeUsers(payload) {
+// if (Array.isArray(payload)) return payload;
+// if (Array.isArray(payload?.users)) return payload.users;
+// if (Array.isArray(payload?.data)) return payload.data;
+// return [];
+// }
+
+// function getVerificationStatus(user) {
+// const status = String(user.status || "").toLowerCase();
+// const approvalStatus = String(user.approvalStatus || "").toLowerCase();
+// const isVerified = Boolean(user.isVerified);
+
+// if (status === "rejected" || approvalStatus === "rejected") {
+// return "rejected";
+// }
+
+// if (
+// status === "verified" ||
+// status === "approved" ||
+// approvalStatus === "verified" ||
+// approvalStatus === "approved" ||
+// isVerified
+// ) {
+// return "verified";
+// }
+
+// return "awaiting";
+// }
+
+// function getLocation(user) {
+// if (typeof user.location === "string" && user.location.trim()) {
+// return user.location;
+// }
+
+// if (user.location && typeof user.location === "object") {
+// return (
+// user.location.city ||
+// user.location.state ||
+// user.location.venue ||
+// "—"
+// );
+// }
+
+// return user.city || user.state || "—";
+// }
+
+// function getDisplayName(user) {
+// return user.fullName || user.name || user.organizationName || "Unnamed Organizer";
+// }
+
+// function badgeText(status) {
+// if (status === "verified") return "Verified";
+// if (status === "rejected") return "Rejected";
+// return "Awaiting Verification";
+// }
+
+// function updatePendingCount() {
+// const count = organizers.filter(
+// (organizer) => organizer._verificationStatus === "awaiting"
+// ).length;
+
+// els.pendingCount.textContent = count;
+// }
+
+// function renderTable() {
+// const query = els.searchInput.value.trim().toLowerCase();
+
+// const filtered = organizers.filter((organizer) => {
+// const matchesFilter =
+// currentFilter === "all"
+// ? true
+// : organizer._verificationStatus === currentFilter;
+
+// const searchableText = `
+// ${getDisplayName(organizer)}
+// ${organizer.email || ""}
+// ${getLocation(organizer)}
+// ${organizer._verificationStatus}
+// `.toLowerCase();
+
+// return matchesFilter && searchableText.includes(query);
+// });
+
+// if (!filtered.length) {
+// els.orgTableWrap.style.display = "none";
+// els.emptyState.style.display = "block";
+// return;
+// }
+
+// els.orgTableWrap.style.display = "table";
+// els.emptyState.style.display = "none";
+
+// els.orgTable.innerHTML = filtered
+// .map((organizer) => {
+// const id = organizer._id || organizer.id || "";
+// const status = organizer._verificationStatus;
+
+// return `
+//
+// ${getDisplayName(organizer)}
+// ${organizer.email || "—"}
+// ${getLocation(organizer)}
+// ${badgeText(status)}
+//
+// View Details
+//
+//
+// `;
+// })
+// .join("");
+
+// attachViewDetailsHandlers();
+// }
+
+// function attachViewDetailsHandlers() {
+// document.querySelectorAll(".view").forEach((button) => {
+// button.addEventListener("click", () => {
+// const organizerId = button.dataset.id;
+// window.location.href = `verification-details.html?id=${encodeURIComponent(organizerId)}`;
+// });
+// });
+// }
+
+// async function loadAdminProfile() {
+// try {
+// let profile;
+
+// try {
+// profile = await apiRequest("/users/me");
+// } catch {
+// profile = await apiRequest("/user/me");
+// }
+
+// els.adminName.textContent =
+// profile.fullName || profile.name || "Admin";
+
+// if (profile.profileImage) {
+// els.adminAvatar.src = profile.profileImage;
+// }
+// } catch (error) {
+// console.error("Failed to load admin profile:", error.message);
+// }
+// }
+
+// async function loadVerificationQueue() {
+// try {
+// let usersPayload;
+
+// try {
+// usersPayload = await apiRequest("/user");
+// } catch {
+// usersPayload = await apiRequest("/users");
+// }
+
+// const users = normalizeUsers(usersPayload);
+
+// organizers = users
+// .filter((user) => String(user.role || "").toLowerCase() === "organizer")
+// .map((user) => ({
+// ...user,
+// _verificationStatus: getVerificationStatus(user)
+// }));
+
+// updatePendingCount();
+// renderTable();
+// } catch (error) {
+// console.error("Failed to load verification queue:", error.message);
+// els.orgTable.innerHTML = `
+//
+// Failed to load verification queue.
+//
+// `;
+// }
+// }
+
+// function bindFilters() {
+// els.filterButtons.forEach((button) => {
+// button.addEventListener("click", () => {
+// els.filterButtons.forEach((btn) => btn.classList.remove("active"));
+// button.classList.add("active");
+// currentFilter = button.dataset.filter;
+// renderTable();
+// });
+// });
+// }
+
+// function openLogoutModal() {
+// els.logoutModal.classList.remove("hidden");
+// }
+
+// function closeLogoutModal() {
+// els.logoutModal.classList.add("hidden");
+// els.confirmLogout.disabled = false;
+// els.confirmLogout.textContent = "Yes, Log out";
+// }
+
+// async function handleLogout() {
+// try {
+// els.confirmLogout.disabled = true;
+// els.confirmLogout.textContent = "Logging out...";
+
+// await apiRequest("/auth/logout", {
+// method: "POST"
+// });
+// } catch (error) {
+// console.warn("Logout failed:", error.message);
+// } finally {
+// localStorage.clear();
+// sessionStorage.clear();
+// window.location.href = "../../index.html";
+// }
+// }
+
+// function bindUI() {
+// els.searchInput.addEventListener("input", renderTable);
+// bindFilters();
+
+// els.logoutBtn.addEventListener("click", openLogoutModal);
+// els.closeLogoutModal.addEventListener("click", closeLogoutModal);
+// els.cancelLogout.addEventListener("click", closeLogoutModal);
+// els.confirmLogout.addEventListener("click", handleLogout);
+
+// els.logoutModal.addEventListener("click", (event) => {
+// if (event.target === els.logoutModal) {
+// closeLogoutModal();
+// }
+// });
+
+// document.addEventListener("keydown", (event) => {
+// if (event.key === "Escape" && !els.logoutModal.classList.contains("hidden")) {
+// closeLogoutModal();
+// }
+// });
+// }
+
+// document.addEventListener("DOMContentLoaded", async () => {
+// bindUI();
+// await loadAdminProfile();
+// await loadVerificationQueue();
+// });
+
+
+
+
+
+
+
+
+
+
+
+const API_BASE_URL = "https://aidloop-backend.onrender.com/api";
+
+const els = {
+ searchInput: document.getElementById("searchInput"),
+ orgTable: document.getElementById("orgTable"),
+ orgTableWrap: document.getElementById("orgTableWrap"),
+ pendingCount: document.getElementById("pendingCount"),
+ adminName: document.getElementById("adminName"),
+ adminAvatar: document.getElementById("adminAvatar"),
+ filterButtons: document.querySelectorAll(".filter-btn"),
+ emptyState: document.getElementById("emptyState"),
+ logoutBtn: document.getElementById("logoutBtn"),
+ logoutModal: document.getElementById("logoutModal"),
+ closeLogoutModal: document.getElementById("closeLogoutModal"),
+ cancelLogout: document.getElementById("cancelLogout"),
+ confirmLogout: document.getElementById("confirmLogout")
+};
+
+let organizers = [];
+let currentFilter = "awaiting";
+
+async function apiRequest(endpoint, options = {}) {
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ ...(options.headers || {})
+ },
+ ...options
+ });
+
+ const contentType = response.headers.get("content-type") || "";
+ const data = contentType.includes("application/json")
+ ? await response.json()
+ : await response.text();
+
+ if (!response.ok) {
+ throw new Error(
+ (data && data.message) ||
+ (data && data.error) ||
+ "Request failed"
+ );
+ }
+
+ return data;
+}
+
+function normalizeUsers(payload) {
+ if (Array.isArray(payload)) return payload;
+ if (Array.isArray(payload?.users)) return payload.users;
+ if (Array.isArray(payload?.data)) return payload.data;
+ return [];
+}
+
+function getVerificationStatus(user) {
+ const status = String(user.status || "").toLowerCase();
+ const approvalStatus = String(user.approvalStatus || "").toLowerCase();
+ const isVerified = Boolean(user.isVerified);
+
+ if (status === "rejected" || approvalStatus === "rejected") {
+ return "rejected";
+ }
+
+ if (
+ status === "approved" ||
+ status === "verified" ||
+ approvalStatus === "approved" ||
+ approvalStatus === "verified" ||
+ isVerified
+ ) {
+ return "approved";
+ }
+
+ return "awaiting";
+}
+
+function getLocation(user) {
+ if (typeof user.location === "string" && user.location.trim()) {
+ return user.location;
+ }
+
+ if (user.location && typeof user.location === "object") {
+ return (
+ user.location.city ||
+ user.location.state ||
+ user.location.venue ||
+ "—"
+ );
+ }
+
+ return user.city || user.state || "—";
+}
+
+function getDisplayName(user) {
+ return user.fullName || user.name || user.organizationName || "Unnamed Organizer";
+}
+
+function badgeText(status) {
+ if (status === "approved") return "Approved";
+ if (status === "rejected") return "Rejected";
+ return "Awaiting Verification";
+}
+
+function getSortDate(user) {
+ return new Date(
+ user.createdAt ||
+ user.updatedAt ||
+ user.dateCreated ||
+ 0
+ ).getTime();
+}
+
+function updatePendingCount() {
+ const count = organizers.filter(
+ (organizer) => organizer._verificationStatus === "awaiting"
+ ).length;
+
+ els.pendingCount.textContent = count;
+}
+
+function renderTable() {
+ const query = els.searchInput.value.trim().toLowerCase();
+
+ const filtered = organizers.filter((organizer) => {
+ const matchesFilter =
+ currentFilter === "all"
+ ? true
+ : organizer._verificationStatus === currentFilter;
+
+ const searchableText = `
+ ${getDisplayName(organizer)}
+ ${organizer.email || ""}
+ ${getLocation(organizer)}
+ ${organizer._verificationStatus}
+ `.toLowerCase();
+
+ return matchesFilter && searchableText.includes(query);
+ });
+
+ if (!filtered.length) {
+ els.orgTableWrap.style.display = "none";
+ els.emptyState.style.display = "block";
+ return;
+ }
+
+ els.orgTableWrap.style.display = "table";
+ els.emptyState.style.display = "none";
+
+ els.orgTable.innerHTML = filtered
+ .map((organizer) => {
+ const id = organizer._id || organizer.id || "";
+ const status = organizer._verificationStatus;
+
+ return `
+
+ ${getDisplayName(organizer)}
+ ${organizer.email || "—"}
+ ${getLocation(organizer)}
+ ${badgeText(status)}
+
+ View Details
+
+
+ `;
+ })
+ .join("");
+
+ attachViewDetailsHandlers();
+}
+
+function attachViewDetailsHandlers() {
+ document.querySelectorAll(".view").forEach((button) => {
+ button.addEventListener("click", () => {
+ const organizerId = button.dataset.id;
+ window.location.href = `verification-details.html?id=${encodeURIComponent(organizerId)}`;
+ });
+ });
+}
+
+async function loadAdminProfile() {
+ try {
+ let profile;
+
+ try {
+ profile = await apiRequest("/users/me");
+ } catch {
+ profile = await apiRequest("/user/me");
+ }
+
+ els.adminName.textContent =
+ profile.fullName || profile.name || "Admin";
+
+ if (profile.profileImage) {
+ els.adminAvatar.src = profile.profileImage;
+ }
+ } catch (error) {
+ console.error("Failed to load admin profile:", error.message);
+ }
+}
+
+async function loadVerificationQueue() {
+ try {
+ let usersPayload;
+
+ try {
+ usersPayload = await apiRequest("/user");
+ } catch {
+ usersPayload = await apiRequest("/users");
+ }
+
+ const users = normalizeUsers(usersPayload);
+
+ organizers = users
+ .filter((user) => String(user.role || "").toLowerCase() === "organizer")
+ .map((user) => ({
+ ...user,
+ _verificationStatus: getVerificationStatus(user)
+ }))
+ .sort((a, b) => getSortDate(b) - getSortDate(a)); // newest first
+
+ updatePendingCount();
+ renderTable();
+ } catch (error) {
+ console.error("Failed to load verification queue:", error.message);
+ els.orgTable.innerHTML = `
+
+ Failed to load verification queue.
+
+ `;
+ }
+}
+
+function bindFilters() {
+ els.filterButtons.forEach((button) => {
+ button.addEventListener("click", () => {
+ els.filterButtons.forEach((btn) => btn.classList.remove("active"));
+ button.classList.add("active");
+ currentFilter = button.dataset.filter;
+ renderTable();
+ });
+ });
+}
+
+function openLogoutModal() {
+ if (!els.logoutModal) return;
+ els.logoutModal.classList.remove("hidden");
+}
+
+function closeLogoutModal() {
+ if (!els.logoutModal) return;
+ els.logoutModal.classList.add("hidden");
+ if (els.confirmLogout) {
+ els.confirmLogout.disabled = false;
+ els.confirmLogout.textContent = "Yes, Log out";
+ }
+}
+
+async function handleLogout() {
+ try {
+ if (els.confirmLogout) {
+ els.confirmLogout.disabled = true;
+ els.confirmLogout.textContent = "Logging out...";
+ }
+
+ await apiRequest("/auth/logout", {
+ method: "POST"
+ });
+ } catch (error) {
+ console.warn("Logout failed:", error.message);
+ } finally {
+ localStorage.clear();
+ sessionStorage.clear();
+ window.location.href = "../../index.html";
+ }
+}
+
+function bindUI() {
+ els.searchInput?.addEventListener("input", renderTable);
+ bindFilters();
+
+ els.logoutBtn?.addEventListener("click", openLogoutModal);
+ els.closeLogoutModal?.addEventListener("click", closeLogoutModal);
+ els.cancelLogout?.addEventListener("click", closeLogoutModal);
+ els.confirmLogout?.addEventListener("click", handleLogout);
+
+ els.logoutModal?.addEventListener("click", (event) => {
+ if (event.target === els.logoutModal) {
+ closeLogoutModal();
+ }
+ });
+
+ document.addEventListener("keydown", (event) => {
+ if (event.key === "Escape" && els.logoutModal && !els.logoutModal.classList.contains("hidden")) {
+ closeLogoutModal();
+ }
+ });
+}
+
+document.addEventListener("DOMContentLoaded", async () => {
+ bindUI();
+ await loadAdminProfile();
+ await loadVerificationQueue();
+});
\ No newline at end of file
diff --git a/Contact.html b/Contact.html
deleted file mode 100644
index e69de29..0000000
diff --git a/HowItWorks.html b/HowItWorks.html
deleted file mode 100644
index 4bd5661..0000000
--- a/HowItWorks.html
+++ /dev/null
@@ -1,247 +0,0 @@
-
-
-
-
-
- AIDLOOP-FRONTENDWEB
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Sign Up
-
-
-
-
-
-
-
-
-
Recruit Verified
-
-
- Volun -teers
-
-
-
for Your Events
-
-
- Create and manage volunteers, connect with verified volunteers,
- track attendance, and reward effort with certificates — all in one
- platform designed to maximize your community impact.
-
-
-
- Sign Up
- Learn More
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
How it Works
-
-
-
-
-
-
-
-
Sign Up
-
Sign up your organization to start hosting
- volunteer events. Submit your details
- and get verified by the platform.
-
-
-
-
-
-
-
-
-
-
Create Volunteer Event
-
- Create volunteer apportunities using a simple
- event template. Add the event details, roles, and
- number of volunteers needed.
-
-
-
-
-
-
-
-
Manage Volunteers
-
- Track registrations, mark attendance, rate
- volunteers, and issue certificates from your
- organizer dashboard.
-
-
-
-
-
-
-
-
-
Benefits
-
-
-
-
-
-
-
-
Verified Volunteers
-
- Connect with volunteers who are registered and
- tracked through the platform
-
-
-
-
-
-
-
-
-
-
Easy Event Creation
-
- Create events quickly using a structured template
- designed for volunteer activities.
-
-
-
-
-
-
-
-
-
-
Attendance Tracking
-
- Monitor volunteer participation and keep records for each event.
-
-
-
-
-
-
-
-
-
-
Volunteer Certificates
-
- Recognize volunteers by issuing certificates after successful
- event participation.
-
-
-
-
-
-
-
-
-
-
-
Start Organizing Volunteer Events Today
-
-
-
- Join organization already using AidLoop to manage volunteer events
- and create meaningful community impact.
-
-
-
Sign Up
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Images/WhatsApp Image 2026-03-08 at 15.32.16.jpeg b/Images/WhatsApp Image 2026-03-08 at 15.32.16.jpeg
deleted file mode 100644
index f251515..0000000
Binary files a/Images/WhatsApp Image 2026-03-08 at 15.32.16.jpeg and /dev/null differ
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 02b72bb..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2026 aidloop
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/Login.html b/Login.html
deleted file mode 100644
index e69de29..0000000
diff --git a/Organizer/certificates/organizer-certificates.css b/Organizer/certificates/organizer-certificates.css
new file mode 100644
index 0000000..1a4be4e
--- /dev/null
+++ b/Organizer/certificates/organizer-certificates.css
@@ -0,0 +1,317 @@
+*{
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #f4f7fb;
+ color: #1f2f46;
+}
+
+.dashboard-layout {
+ display: flex;
+ min-height: 100vh;
+}
+
+.sidebar {
+ width: 15rem;
+ background: #1f3b63;
+ color: #ffffff;
+ padding: 1.5rem 1rem;
+ flex-shrink: 0;
+}
+
+.sidebar-logo {
+ text-align: center;
+ margin-bottom: 2rem;
+}
+
+.sidebar-logo img {
+ width: 5.5rem;
+ max-width: 100%;
+}
+
+.sidebar-menu {
+ list-style: none;
+ display: flex;
+ flex-direction: column;
+ gap: 0.55rem;
+}
+
+.sidebar-menu li a,
+.sidebar-logout {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+ color: #ffffff;
+ text-decoration: none;
+ padding: 0.95rem 1rem;
+ border-radius: 0.65rem;
+ font-size: 0.96rem;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+}
+
+.sidebar-menu li.active a,
+.sidebar-menu li a:hover {
+ background: #1f80ea;
+}
+
+.sidebar-logout:hover {
+ background: #dc2626;
+}
+
+.main-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.topbar {
+ background: #ffffff;
+ padding: 2rem 2.2rem 1.2rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+}
+
+.page-title {
+ font-size: 2rem;
+ font-weight: 700;
+ color: #223f6b;
+}
+
+.page-subtitle {
+ margin-top: 0.3rem;
+ font-size: 0.9rem;
+ color: #6b7280;
+}
+
+.topbar-right {
+ display: flex;
+ align-items: center;
+ gap: 0.9rem;
+}
+
+.icon-btn {
+ width: 2.7rem;
+ height: 2.7rem;
+ border: none;
+ border-radius: 50%;
+ background: transparent;
+ color: #223f6b;
+ font-size: 1.35rem;
+ cursor: pointer;
+}
+
+.page-content {
+ padding: 2.2rem;
+}
+
+.stats-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 1.4rem;
+ max-width: 40rem;
+ margin-bottom: 2rem;
+}
+
+.stat-card {
+ background: #ffffff;
+ border-radius: 0.9rem;
+ padding: 1.2rem;
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ box-shadow: 0 0.15rem 0.45rem rgba(0, 0, 0, 0.05);
+}
+
+.stat-icon {
+ width: 3rem;
+ height: 3rem;
+ border-radius: 0.7rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.15rem;
+ flex-shrink: 0;
+}
+
+.stat-icon.gold {
+ background: #fff0d8;
+ color: #d29a1f;
+}
+
+.stat-icon.green {
+ background: #dff2e4;
+ color: #2e8b57;
+}
+
+.stat-icon.pink {
+ background: #f8d9dd;
+ color: #a23c4b;
+}
+
+.stat-text p {
+ font-size: 0.72rem;
+ color: #8a94a6;
+ font-weight: 600;
+}
+
+.stat-text h3 {
+ font-size: 1.9rem;
+ color: #223f6b;
+ font-weight: 700;
+ margin-top: 0.1rem;
+}
+
+.table-wrapper {
+ background: #ffffff;
+ border-radius: 0.9rem;
+ overflow: hidden;
+ border: 0.08rem solid #d8dfe8;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+thead {
+ background: #eef2f6;
+}
+
+th,
+td {
+ padding: 1rem;
+ text-align: left;
+ font-size: 0.92rem;
+}
+
+th {
+ color: #35527a;
+ font-weight: 600;
+}
+
+tbody tr {
+ border-bottom: 0.08rem solid #d7dde6;
+}
+
+tbody tr:last-child {
+ border-bottom: none;
+}
+
+.person-cell {
+ display: flex;
+ align-items: center;
+ gap: 0.8rem;
+}
+
+.avatar {
+ width: 2.2rem;
+ height: 2.2rem;
+ border-radius: 50%;
+ object-fit: cover;
+ flex-shrink: 0;
+}
+
+.status-badge {
+ display: inline-block;
+ min-width: 6.8rem;
+ text-align: center;
+ padding: 0.58rem 0.95rem;
+ border-radius: 0.42rem;
+ color: #ffffff;
+ font-size: 0.84rem;
+ font-weight: 600;
+}
+
+.status-issued {
+ background: #2e8b57;
+}
+
+.status-pending {
+ background: #f5ae42;
+}
+
+.row-actions {
+ text-align: center;
+}
+
+.row-actions button,
+.row-actions a {
+ border: none;
+ background: transparent;
+ color: #223f6b;
+ font-size: 1.1rem;
+ cursor: pointer;
+ text-decoration: none;
+}
+
+.table-footer {
+ margin-top: 1rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 1rem;
+ flex-wrap: wrap;
+ color: #4b5563;
+ font-size: 0.9rem;
+}
+
+.pagination {
+ display: flex;
+ align-items: center;
+ gap: 0.35rem;
+ flex-wrap: wrap;
+}
+
+.page-btn {
+ border: 0.08rem solid #d6dde6;
+ background: #ffffff;
+ color: #1f2f46;
+ padding: 0.42rem 0.75rem;
+ border-radius: 0.3rem;
+ cursor: pointer;
+ font-size: 0.8rem;
+}
+
+.page-btn.active {
+ background: #1f80ea;
+ color: #ffffff;
+ border-color: #1f80ea;
+}
+
+@media (max-width: 68rem) {
+ .stats-grid {
+ grid-template-columns: 1fr;
+ max-width: 100%;
+ }
+}
+
+@media (max-width: 64rem) {
+ .dashboard-layout {
+ flex-direction: column;
+ }
+
+ .sidebar {
+ width: 100%;
+ }
+
+ .topbar {
+ flex-direction: column;
+ gap: 1rem;
+ }
+
+ .table-wrapper {
+ overflow-x: auto;
+ }
+
+ table {
+ min-width: 46rem;
+ }
+}
\ No newline at end of file
diff --git a/Organizer/certificates/organizer-certificates.html b/Organizer/certificates/organizer-certificates.html
new file mode 100644
index 0000000..e182e62
--- /dev/null
+++ b/Organizer/certificates/organizer-certificates.html
@@ -0,0 +1,169 @@
+
+
+
+
+
+ AIDLoop Organizer Certificates
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
TOTAL CERTIFICATES
+
0
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+ EVENT
+ Date
+ Status
+
+
+
+
+
+
+ Loading certificates...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Organizer/certificates/organizer-certificates.js b/Organizer/certificates/organizer-certificates.js
new file mode 100644
index 0000000..2ee07c8
--- /dev/null
+++ b/Organizer/certificates/organizer-certificates.js
@@ -0,0 +1,93 @@
+import { apiRequest, normalizeArray } from "../../assets/js/api.js";
+import { requireOrganizer } from "../../assets/js/auth.js";
+import { logout } from "../../assets/js/logout.js";
+import { ROUTES } from "../../assets/js/config.js";
+import { formatDate } from "../../assets/js/utils.js";
+
+const els = {
+ totalCertificates: document.getElementById("totalCertificates"),
+ issuedCertificates: document.getElementById("issuedCertificates"),
+ pendingCertificates: document.getElementById("pendingCertificates"),
+ certificatesTable: document.getElementById("certificatesTable"),
+ tableCountText: document.getElementById("tableCountText"),
+ logoutBtn: document.getElementById("logoutBtn")
+};
+
+function rowKey(userId, eventId) { return `${String(userId)}::${String(eventId)}`; }
+function avatar(user) { return user?.profileImage || "https://i.pravatar.cc/100?img=12"; }
+
+function normalizeIssued(records) {
+ return records.map((item) => ({
+ status: "issued",
+ certificateId: item._id || item.id || item.certificateId || "",
+ userId: item.user?._id || item.user?.id || item.volunteer?._id || item.volunteer?.id || item.userId || "",
+ eventId: item.event?._id || item.event?.id || item.eventId || "",
+ userName: item.user?.fullName || item.user?.name || item.volunteer?.fullName || item.volunteer?.name || item.volunteerName || "Unknown Volunteer",
+ userAvatar: avatar(item.user || item.volunteer),
+ eventName: item.event?.name || item.eventName || "Untitled Event",
+ date: item.issuedAt || item.createdAt || item.event?.date || ""
+ }));
+}
+
+function render(rows) {
+ els.totalCertificates.textContent = rows.length;
+ els.issuedCertificates.textContent = rows.filter((r) => r.status === "issued").length;
+ els.pendingCertificates.textContent = rows.filter((r) => r.status === "pending").length;
+ els.tableCountText.textContent = `Showing ${rows.length} of ${rows.length} certificates`;
+ els.certificatesTable.innerHTML = rows.map((row) => `
+
+ ${row.userName}
+ ${row.eventName}
+ ${formatDate(row.date, "long")}
+ ${row.status === "issued" ? "Issued" : "Pending"}
+ ${row.status === "issued" && row.certificateId ? `View ` : "..."}
+
+ `).join("") || `No certificate records found. `;
+}
+
+document.addEventListener("DOMContentLoaded", async () => {
+ const organizer = await requireOrganizer();
+ if (!organizer) return;
+ els.logoutBtn.addEventListener("click", () => logout(ROUTES.home));
+
+ try {
+ const eventsPayload = await apiRequest("/events");
+ const allEvents = normalizeArray(eventsPayload, ["events"]);
+ const organizerId = String(organizer._id || organizer.id || "");
+ const ownEvents = allEvents.filter((event) => {
+ if (typeof event.organizer === "object" && event.organizer) {
+ return String(event.organizer._id || event.organizer.id || "") === organizerId;
+ }
+ return String(event.organizerId || "") === organizerId;
+ });
+
+ const registrations = [];
+ for (const event of ownEvents) {
+ const data = await apiRequest(`/applications/events/${event._id || event.id}/registrations`);
+ const regs = Array.isArray(data) ? data : data.data || [];
+ regs.forEach((reg) => registrations.push({ ...reg, _eventId: event._id || event.id, _eventName: event.name || "Untitled Event", _eventDate: event.date }));
+ }
+
+ let issuedPayload = [];
+ try { issuedPayload = await apiRequest("/certificates/my-certificates"); } catch { issuedPayload = []; }
+ const issuedRows = normalizeIssued(normalizeArray(issuedPayload, ["certificates"]));
+ const issuedKeys = new Set(issuedRows.map((r) => rowKey(r.userId, r.eventId)));
+
+ const pendingRows = registrations
+ .filter((reg) => !issuedKeys.has(rowKey(reg.user?._id || reg.user?.id || "", reg._eventId || "")))
+ .map((reg) => ({
+ status: "pending",
+ certificateId: "",
+ userId: reg.user?._id || reg.user?.id || "",
+ eventId: reg._eventId,
+ userName: reg.user?.fullName || reg.user?.name || "Unknown Volunteer",
+ userAvatar: avatar(reg.user),
+ eventName: reg._eventName,
+ date: reg._eventDate || reg.createdAt || ""
+ }));
+
+ render([...issuedRows, ...pendingRows].sort((a, b) => new Date(b.date || 0) - new Date(a.date || 0)));
+ } catch {
+ els.certificatesTable.innerHTML = `Failed to load certificates. `;
+ }
+});
diff --git a/Organizer/dashboard/organizer-dashboard.css b/Organizer/dashboard/organizer-dashboard.css
new file mode 100644
index 0000000..242868d
--- /dev/null
+++ b/Organizer/dashboard/organizer-dashboard.css
@@ -0,0 +1,277 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #f4f7fb;
+ color: #1f2f46;
+}
+
+.dashboard-layout {
+ display: flex;
+ min-height: 100vh;
+}
+
+.sidebar {
+ width: 15.5rem;
+ background: #1f3b63;
+ color: #fff;
+ padding: 1.5rem 1rem;
+ flex-shrink: 0;
+}
+
+.sidebar-logo {
+ text-align: center;
+ margin-bottom: 2rem;
+}
+
+.sidebar-logo img {
+ width: 6rem;
+}
+
+.sidebar-menu {
+ list-style: none;
+ display: flex;
+ flex-direction: column;
+ gap: 0.55rem;
+}
+
+.sidebar-menu li a,
+.sidebar-logout {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+ color: #fff;
+ text-decoration: none;
+ padding: 0.95rem 1rem;
+ border-radius: 0.65rem;
+ font-size: 0.98rem;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+}
+
+.sidebar-menu li.active a,
+.sidebar-menu li a:hover {
+ background: #1f80ea;
+}
+
+.sidebar-logout:hover {
+ background: #dc2626;
+}
+
+.main-content {
+ flex: 1;
+ padding: 0;
+}
+
+.topbar {
+ background: #fff;
+ padding: 2rem 2rem 1.2rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+}
+
+.welcome-title {
+ font-size: 2rem;
+ font-weight: 700;
+ color: #223f6b;
+}
+
+.welcome-subtitle {
+ color: #b7bec8;
+ font-size: 0.9rem;
+ margin-top: 0.3rem;
+}
+
+.topbar-actions {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.create-event-btn {
+ background: #223f6b;
+ color: #fff;
+ text-decoration: none;
+ padding: 1rem 1.5rem;
+ border-radius: 0.8rem;
+ font-weight: 600;
+}
+
+.icon-btn {
+ width: 2.8rem;
+ height: 2.8rem;
+ border: none;
+ border-radius: 50%;
+ background: transparent;
+ color: #223f6b;
+ font-size: 1.5rem;
+ cursor: pointer;
+}
+
+.profile-mini img {
+ width: 2.6rem;
+ height: 2.6rem;
+ border-radius: 50%;
+ object-fit: cover;
+}
+
+.dashboard-section,
+.recent-events-section {
+ padding: 2rem;
+}
+
+.dashboard-section h2,
+.recent-events-section h2 {
+ font-size: 1.9rem;
+ color: #223f6b;
+ margin-bottom: 1.5rem;
+}
+
+.stats-grid {
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 1.5rem;
+}
+
+.stat-card {
+ background: #fff;
+ border: 0.08rem solid #9ca9bb;
+ border-radius: 0.9rem;
+ overflow: hidden;
+}
+
+.stat-number {
+ text-align: center;
+ font-size: 2.2rem;
+ font-weight: 700;
+ padding: 1rem 0;
+ color: #111;
+}
+
+.stat-footer {
+ border-top: 0.08rem solid #9ca9bb;
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+ padding: 0.95rem 1rem;
+ color: #526176;
+ font-size: 0.82rem;
+}
+
+.stat-footer i {
+ color: #1f80ea;
+ font-size: 1.15rem;
+}
+
+.section-divider {
+ height: 0.08rem;
+ background: #aeb8c6;
+ margin-bottom: 2rem;
+}
+
+.table-wrapper {
+ background: #fff;
+ border-radius: 0.9rem;
+ overflow: hidden;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+thead {
+ background: #eef2f6;
+}
+
+th, td {
+ padding: 1.2rem 1.1rem;
+ text-align: left;
+ font-size: 0.95rem;
+}
+
+th {
+ color: #35527a;
+ font-weight: 600;
+}
+
+tbody tr {
+ border-bottom: 0.08rem solid #d7dde6;
+}
+
+tbody tr:last-child {
+ border-bottom: none;
+}
+
+.status-badge {
+ display: inline-block;
+ min-width: 7rem;
+ text-align: center;
+ padding: 0.65rem 1rem;
+ border-radius: 0.45rem;
+ color: #fff;
+ font-size: 0.9rem;
+ font-weight: 600;
+}
+
+.status-upcoming {
+ background: #f5ae42;
+}
+
+.status-published {
+ background: #26a65b;
+}
+
+.status-draft {
+ background: #6f809b;
+}
+
+.status-completed {
+ background: #1f80ea;
+}
+
+.row-actions {
+ color: #223f6b;
+ font-size: 1.3rem;
+ text-align: center;
+}
+
+@media (max-width: 68rem) {
+ .stats-grid {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+}
+
+@media (max-width: 52rem) {
+ .dashboard-layout {
+ flex-direction: column;
+ }
+
+ .sidebar {
+ width: 100%;
+ }
+
+ .topbar {
+ flex-direction: column;
+ gap: 1rem;
+ }
+
+ .stats-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .table-wrapper {
+ overflow-x: auto;
+ }
+
+ table {
+ min-width: 52rem;
+ }
+}
diff --git a/Organizer/dashboard/organizer-dashboard.html b/Organizer/dashboard/organizer-dashboard.html
new file mode 100644
index 0000000..65562d6
--- /dev/null
+++ b/Organizer/dashboard/organizer-dashboard.html
@@ -0,0 +1,121 @@
+
+
+
+
+
+ AIDLoop Organizer Dashboard
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Recent Events
+
+
+
+
+
+ Events Name
+ Location
+ Date
+ Volunteers
+ Status
+
+
+
+
+
+ Loading events...
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Organizer/dashboard/organizer-dashboard.js b/Organizer/dashboard/organizer-dashboard.js
new file mode 100644
index 0000000..1375a5e
--- /dev/null
+++ b/Organizer/dashboard/organizer-dashboard.js
@@ -0,0 +1,63 @@
+import { apiRequest, normalizeArray } from "../../assets/js/api.js";
+import { requireOrganizer } from "../../assets/js/auth.js";
+import { logout } from "../../assets/js/logout.js";
+import { ROUTES } from "../../assets/js/config.js";
+import { formatDate, getLocationText } from "../../assets/js/utils.js";
+
+const els = {
+ totalEvents: document.getElementById("totalEvents"),
+ upcomingEvents: document.getElementById("upcomingEvents"),
+ completedEvents: document.getElementById("completedEvents"),
+ totalVolunteers: document.getElementById("totalVolunteers"),
+ eventsTable: document.getElementById("eventsTable"),
+ logoutBtn: document.getElementById("logoutBtn")
+};
+
+let organizer;
+
+function getStatus(event) {
+ const raw = String(event.status || "").toLowerCase();
+ if (raw === "draft") return "draft";
+ if (raw === "cancelled" || raw === "canceled") return "cancelled";
+ const eventDate = event.date ? new Date(event.date) : null;
+ if (raw === "published" && eventDate && eventDate < new Date()) return "completed";
+ if (raw === "published") return "published";
+ return "published";
+}
+
+document.addEventListener("DOMContentLoaded", async () => {
+ organizer = await requireOrganizer();
+ if (!organizer) return;
+
+ els.logoutBtn.addEventListener("click", () => logout(ROUTES.home));
+
+ try {
+ const payload = await apiRequest("/events");
+ const allEvents = normalizeArray(payload, ["events"]);
+ const organizerId = String(organizer._id || organizer.id || "");
+ const events = allEvents.filter((event) => {
+ if (typeof event.organizer === "object" && event.organizer) {
+ return String(event.organizer._id || event.organizer.id || "") === organizerId;
+ }
+ return String(event.organizerId || "") === organizerId;
+ });
+
+ const totalVolunteers = events.reduce((sum, event) => sum + (event.filledSlots ?? event.registrationsCount ?? 0), 0);
+ els.totalEvents.textContent = events.length;
+ els.upcomingEvents.textContent = events.filter((e) => getStatus(e) === "published").length;
+ els.completedEvents.textContent = events.filter((e) => getStatus(e) === "completed").length;
+ els.totalVolunteers.textContent = totalVolunteers;
+
+ els.eventsTable.innerHTML = events.slice(0, 5).map((event) => `
+
+ ${event.name || "Untitled Event"}
+ ${getLocationText(event)}
+ ${formatDate(event.date, "long")}
+ ${event.filledSlots ?? event.registrationsCount ?? 0}/${event.volunteerSlots ?? 0}
+ ${getStatus(event)}
+
+ `).join("") || `No events found. `;
+ } catch {
+ els.eventsTable.innerHTML = `Failed to load dashboard data. `;
+ }
+});
diff --git a/Organizer/email-verified/email-verified.css b/Organizer/email-verified/email-verified.css
new file mode 100644
index 0000000..5abfded
--- /dev/null
+++ b/Organizer/email-verified/email-verified.css
@@ -0,0 +1,160 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #f5f7fa;
+ min-height: 100vh;
+}
+
+.verified-page {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 2rem;
+}
+
+.verified-card {
+ width: 100%;
+ max-width: 40rem;
+ min-height: 46rem;
+ background: #ffffff;
+ padding: 4rem 2rem;
+ text-align: center;
+ box-shadow: 0 0.625rem 2.5rem rgba(0, 0, 0, 0.05);
+}
+
+.icon-wrap {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 1.5rem;
+}
+
+.success-icon {
+ width: 7rem;
+ height: 7rem;
+ border-radius: 50%;
+ background: #79e36d;
+ border: 0.1875rem solid #1fc933;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ clip-path: polygon(
+ 50% 0%, 61% 8%, 75% 8%, 82% 20%, 95% 25%, 92% 39%, 100% 50%,
+ 92% 61%, 95% 75%, 82% 80%, 75% 92%, 61% 92%, 50% 100%, 39% 92%,
+ 25% 92%, 18% 80%, 5% 75%, 8% 61%, 0% 50%, 8% 39%, 5% 25%, 18% 20%,
+ 25% 8%, 39% 8%
+ );
+}
+
+.success-icon i {
+ font-size: 2.3rem;
+ color: #ffffff;
+}
+
+.verified-card h1 {
+ font-size: 2rem;
+ color: #223f6b;
+ margin-bottom: 1.2rem;
+ font-weight: 700;
+}
+
+.mini-text {
+ min-height: 1rem;
+ margin-bottom: 2rem;
+ font-size: 0.8rem;
+ color: #6b7280;
+}
+
+.divider {
+ width: 100%;
+ height: 0.0625rem;
+ background: #cfd5dc;
+ margin: 2rem 0 1.5rem;
+}
+
+.message {
+ max-width: 26rem;
+ margin: 0 auto 2rem;
+ font-size: 1.25rem;
+ line-height: 1.7;
+ color: #444444;
+}
+
+.change-email-link {
+ display: inline-block;
+ margin-bottom: 1rem;
+ font-size: 1.15rem;
+ color: #0d6efd;
+ text-decoration: none;
+ font-weight: 500;
+}
+
+.change-email-link:hover {
+ text-decoration: underline;
+}
+
+.back-link {
+ display: inline-block;
+ margin: 0.5rem 0 3rem;
+ font-size: 1.2rem;
+ color: #8b8b8b;
+ text-decoration: none;
+}
+
+.back-link:hover {
+ color: #223f6b;
+}
+
+.primary-btn {
+ width: 100%;
+ max-width: 28rem;
+ border: none;
+ background: #223f6b;
+ color: #ffffff;
+ padding: 1rem 1.25rem;
+ border-radius: 0.75rem;
+ font-size: 1.1rem;
+ font-weight: 600;
+ cursor: pointer;
+}
+
+.primary-btn:hover {
+ opacity: 0.95;
+}
+
+.primary-btn:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+}
+
+@media (max-width: 40rem) {
+ .verified-card {
+ min-height: auto;
+ padding: 3rem 1.25rem;
+ }
+
+ .verified-card h1 {
+ font-size: 1.6rem;
+ }
+
+ .message {
+ font-size: 1rem;
+ }
+
+ .change-email-link,
+ .back-link {
+ font-size: 1rem;
+ }
+
+ .success-icon {
+ width: 6rem;
+ height: 6rem;
+ }
+}
+
diff --git a/Organizer/email-verified/email-verified.html b/Organizer/email-verified/email-verified.html
new file mode 100644
index 0000000..ed0eaf4
--- /dev/null
+++ b/Organizer/email-verified/email-verified.html
@@ -0,0 +1,52 @@
+
+
+
+
+
+ Email Verified Successfully
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Organizer/email-verified/email-verified.js b/Organizer/email-verified/email-verified.js
new file mode 100644
index 0000000..d41b16f
--- /dev/null
+++ b/Organizer/email-verified/email-verified.js
@@ -0,0 +1,56 @@
+import { apiRequest } from "../../assets/js/api.js";
+import { ROUTES } from "../../assets/js/config.js";
+
+const els = {
+ continueBtn: document.getElementById("continueBtn"),
+ statusText: document.getElementById("statusText")
+};
+
+function setLoading(isLoading) {
+ els.continueBtn.disabled = isLoading;
+ els.continueBtn.textContent = isLoading
+ ? "Checking session..."
+ : "Continue to Dashboard";
+}
+
+async function checkUserSession() {
+ try {
+ setLoading(true);
+
+ let user;
+ try {
+ user = await apiRequest("/users/me");
+ } catch {
+ user = await apiRequest("/user/me");
+ }
+
+ if (user && String(user.role || "").toLowerCase() === "organizer") {
+ els.statusText.textContent = "You are already signed in.";
+ return true;
+ }
+
+ els.statusText.textContent = "";
+ return false;
+ } catch {
+ els.statusText.textContent = "";
+ return false;
+ } finally {
+ setLoading(false);
+ }
+}
+
+els.continueBtn.addEventListener("click", async () => {
+ const hasSession = await checkUserSession();
+
+ if (hasSession) {
+ window.location.href = ROUTES.organizerDashboard;
+ return;
+ }
+
+ window.location.href = ROUTES.organizerLogin;
+});
+
+document.addEventListener("DOMContentLoaded", async () => {
+ await checkUserSession();
+});
+
diff --git a/Organizer/events/cancel-event.css b/Organizer/events/cancel-event.css
new file mode 100644
index 0000000..27b4ba6
--- /dev/null
+++ b/Organizer/events/cancel-event.css
@@ -0,0 +1,133 @@
+
+
+.page-title {
+ font-size: 2rem;
+ font-weight: 700;
+ color: #223f6b;
+}
+
+
+.cancel-section {
+ padding: 2rem;
+ max-width: 700px;
+}
+
+.warning {
+ text-align: center;
+ margin-bottom: 2rem;
+}
+
+.warning i {
+ font-size: 3rem;
+ color: orange;
+}
+
+.warning p {
+ margin-top: 1rem;
+ color: #333;
+}
+
+.reasons h3 {
+ margin-bottom: 1rem;
+}
+
+.checkbox-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 0.8rem;
+}
+
+.reason-text {
+ margin-top: 2rem;
+}
+
+textarea {
+ width: 100%;
+ height: 120px;
+ padding: 0.8rem;
+ border-radius: 8px;
+ border: 1px solid #ccc;
+}
+
+.actions {
+ display: flex;
+ gap: 1rem;
+ margin-top: 2rem;
+}
+
+.btn {
+ padding: 0.8rem 1.4rem;
+ border-radius: 6px;
+ border: none;
+ cursor: pointer;
+}
+
+.btn.danger {
+ background: red;
+ color: #fff;
+}
+
+.btn.secondary {
+ background: #2c4a74;
+ color: #fff;
+}
+
+/* MODAL */
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0,0,0,0.4);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 999;
+}
+
+.hidden {
+ display: none;
+}
+
+.modal-card {
+ background: #fff;
+ padding: 2rem;
+ border-radius: 16px;
+ width: 400px;
+ max-width: 90%;
+ text-align: center;
+ position: relative;
+}
+
+.close-btn {
+ position: absolute;
+ top: 10px;
+ right: 14px;
+ font-size: 1.4rem;
+ border: none;
+ background: none;
+ cursor: pointer;
+}
+
+.warning-icon {
+ font-size: 3rem;
+ color: orange;
+ margin-bottom: 1rem;
+}
+
+.modal-content p {
+ margin-bottom: 2rem;
+ color: #333;
+}
+
+.btn.full {
+ width: 100%;
+ margin-bottom: 1rem;
+}
+
+.btn.outline {
+ background: transparent;
+ border: 2px solid #2c4a74;
+ color: #2c4a74;
+}
\ No newline at end of file
diff --git a/Organizer/events/cancel-event.html b/Organizer/events/cancel-event.html
new file mode 100644
index 0000000..ca235a9
--- /dev/null
+++ b/Organizer/events/cancel-event.html
@@ -0,0 +1,140 @@
+
+
+
+
+
+ Cancel Event
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cancelling this event will notify all registered volunteers and cannot be undone
+
+
+
+
+
+
+
+
+ Why are you cancelling this event?
+
+
+
+
+
+ Cancel Event
+ Go Back
+
+
+
+
+
+
+
+
+
+
+
×
+
+
+
+
+
+ Are you sure you want to cancel this event?
+ This cannot be undone and all volunteers will be notified.
+
+
+
+ Yes, Cancel event
+
+
+
+ No, Go back
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Organizer/events/cancel-event.js b/Organizer/events/cancel-event.js
new file mode 100644
index 0000000..d72993c
--- /dev/null
+++ b/Organizer/events/cancel-event.js
@@ -0,0 +1,87 @@
+import { apiRequest } from "../../assets/js/api.js";
+import { requireRole } from "../../assets/js/auth.js";
+import { logout } from "../../assets/js/logout.js";
+import { ROUTES } from "../../assets/js/config.js";
+
+const eventId = new URLSearchParams(window.location.search).get("id");
+
+const els = {
+ cancelBtn: document.getElementById("cancelEventBtn"),
+ goBackBtn: document.getElementById("goBackBtn"),
+ reasonText: document.getElementById("reasonText"),
+ logoutBtn: document.getElementById("logoutBtn"),
+ confirmModal: document.getElementById("confirmModal"),
+ confirmCancel: document.getElementById("confirmCancel"),
+ closeModal: document.getElementById("closeModal"),
+ cancelModal: document.getElementById("cancelModal")
+};
+
+function getSelectedReasons() {
+ return [...document.querySelectorAll("input[type='checkbox']:checked")]
+ .map((cb) => cb.value);
+}
+
+function openModal() {
+ els.confirmModal.classList.remove("hidden");
+}
+
+function hideModal() {
+ els.confirmModal.classList.add("hidden");
+}
+
+async function cancelEvent() {
+ const reasons = getSelectedReasons();
+ const text = els.reasonText.value.trim();
+
+ if (!reasons.length && !text) {
+ alert("Please provide a reason");
+ hideModal();
+ return;
+ }
+
+ const reason = [...reasons, text].filter(Boolean).join(", ");
+
+ try {
+ els.confirmCancel.disabled = true;
+ els.confirmCancel.textContent = "Cancelling...";
+
+ await apiRequest(`/events/${eventId}/cancel`, {
+ method: "PATCH",
+ body: JSON.stringify({ reason })
+ });
+
+ alert("Event cancelled successfully");
+ window.location.href = ROUTES.eventListing;
+ } catch (err) {
+ alert(err.message || "Failed to cancel event");
+ } finally {
+ els.confirmCancel.disabled = false;
+ els.confirmCancel.textContent = "Yes, Cancel event";
+ }
+}
+
+els.cancelBtn.addEventListener("click", openModal);
+
+els.goBackBtn.addEventListener("click", () => {
+ window.history.back();
+});
+
+[els.closeModal, els.cancelModal].forEach((btn) => {
+ btn.addEventListener("click", hideModal);
+});
+
+els.confirmCancel.addEventListener("click", cancelEvent);
+
+els.logoutBtn.addEventListener("click", () => {
+ logout(ROUTES.organizerLogin);
+});
+
+document.addEventListener("DOMContentLoaded", async () => {
+ await requireRole("organizer", ROUTES.organizerLogin);
+
+ if (!eventId) {
+ alert("Invalid event");
+ window.location.href = ROUTES.eventListing;
+ return;
+ }
+});
\ No newline at end of file
diff --git a/Organizer/events/create-event.css b/Organizer/events/create-event.css
new file mode 100644
index 0000000..58ae06a
--- /dev/null
+++ b/Organizer/events/create-event.css
@@ -0,0 +1,111 @@
+
+.page-title {
+ font-size: 2rem;
+ font-weight: 700;
+ color: #223f6b;
+}
+
+.page-subtitle {
+ color: #6b7280;
+ font-size: 0.9rem;
+ margin-top: 0.3rem;
+}
+
+.topbar-actions {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.create-event-btn {
+ background: #223f6b;
+ color: #fff;
+ text-decoration: none;
+ padding: 0.95rem 1.4rem;
+ border-radius: 0.75rem;
+ font-weight: 600;
+ cursor: pointer;
+}
+
+.icon-btn {
+ width: 2.6rem;
+ height: 2.6rem;
+ border: none;
+ border-radius: 50%;
+ background: transparent;
+ color: #223f6b;
+ font-size: 1.35rem;
+ cursor: pointer;
+}
+
+.profile-mini img {
+ width: 2.5rem;
+ height: 2.5rem;
+ border-radius: 50%;
+ object-fit: cover;
+}
+
+
+.form-section {
+ padding: 2rem;
+ max-width: 600px;
+}
+
+.image-upload {
+ border: 2px dashed #1f80ea;
+ padding: 2rem;
+ text-align: center;
+ margin-bottom: 1.5rem;
+ cursor: pointer;
+}
+
+input, textarea {
+ width: 100%;
+ padding: 0.7rem;
+ margin: 0.5rem 0 1rem;
+ border-radius: 6px;
+ border: 1px solid #ccc;
+}
+
+.grid-2 {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 1rem;
+}
+
+.tags-input {
+ display: flex;
+ gap: 5px;
+}
+
+#rolesList span {
+ background: #1f80ea;
+ color: white;
+ padding: 5px 10px;
+ margin: 5px;
+ display: inline-block;
+ border-radius: 5px;
+}
+
+.actions {
+ display: flex;
+ justify-content: space-between;
+ margin-top: 20px;
+}
+
+.actions button {
+ padding: 10px 20px;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+}
+
+.actions button:first-child {
+ background: transparent;
+ border: 1px solid #1f80ea;
+}
+
+.actions button:last-child {
+ background: #1f3b63;
+ color: white;
+}
diff --git a/Organizer/events/create-event.html b/Organizer/events/create-event.html
new file mode 100644
index 0000000..cc1751a
--- /dev/null
+++ b/Organizer/events/create-event.html
@@ -0,0 +1,167 @@
+
+
+
+
+
+ Create Event
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Organizer/events/create-event.js b/Organizer/events/create-event.js
new file mode 100644
index 0000000..a6155f3
--- /dev/null
+++ b/Organizer/events/create-event.js
@@ -0,0 +1,279 @@
+// import { apiRequest } from "../../assets/js/api.js";
+// import { ROUTES } from "../../assets/js/config.js";
+// import { logout } from "../../assets/js/logout.js";
+
+// const roles = [];
+// let imageUrl = "";
+
+// const els = {
+// form: document.getElementById("eventForm"),
+// roleInput: document.getElementById("roleInput"),
+// addRole: document.getElementById("addRole"),
+// rolesList: document.getElementById("rolesList"),
+// imageInput: document.getElementById("imageInput"),
+// imageBox: document.getElementById("imageBox"),
+// formMsg: document.getElementById("formMsg"),
+// saveDraft: document.getElementById("saveDraft"),
+// logoutBtn: document.getElementById("logoutBtn")
+// };
+
+// /* IMAGE */
+// els.imageBox.addEventListener("click", () => els.imageInput.click());
+
+// els.imageInput.addEventListener("change", async (e) => {
+// const file = e.target.files[0];
+// if (!file) return;
+
+// // TEMP: Replace with Cloudinary later
+// imageUrl = URL.createObjectURL(file);
+// els.imageBox.innerHTML = ` `;
+// });
+
+// /* ROLES */
+// els.addRole.addEventListener("click", () => {
+// const val = els.roleInput.value.trim();
+// if (!val) return;
+
+// roles.push(val);
+// els.roleInput.value = "";
+// renderRoles();
+// });
+
+// function renderRoles() {
+// els.rolesList.innerHTML = roles.map(r => `${r} `).join("");
+// }
+
+// /* CREATE EVENT */
+// async function createEvent(status = "draft") {
+// const payload = {
+// name: document.getElementById("name").value,
+// category: document.getElementById("category").value,
+// description: document.getElementById("description").value,
+// location: {
+// venue: document.getElementById("venue").value,
+// city: document.getElementById("city").value
+// },
+// image: imageUrl,
+// date: document.getElementById("date").value,
+// startTime: document.getElementById("startTime").value,
+// endTime: document.getElementById("endTime").value,
+// volunteerSlots: Number(document.getElementById("slots").value),
+// roles,
+// certificateEnabled: document.getElementById("certificateEnabled").checked,
+// requirements: document
+// .getElementById("requirements")
+// .value.split("\n")
+// };
+
+// try {
+// const res = await apiRequest("/events", {
+// method: "POST",
+// body: JSON.stringify(payload)
+// });
+
+// const eventId = res._id || res.id;
+
+// if (status === "published") {
+// await apiRequest(`/events/${eventId}/status`, {
+// method: "PATCH",
+// body: JSON.stringify({ status: "published" })
+// });
+// }
+
+// els.formMsg.textContent = "Event created successfully!";
+// window.location.href = ROUTES.organizerDashboard;
+
+// } catch (err) {
+// els.formMsg.textContent = err.message;
+// }
+// }
+
+// /* ACTIONS */
+// els.form.addEventListener("submit", (e) => {
+// e.preventDefault();
+// createEvent("published");
+// });
+
+// els.saveDraft.addEventListener("click", () => createEvent("draft"));
+
+// /* LOGOUT */
+// els.logoutBtn.addEventListener("click", () => {
+// logout(ROUTES.organizerLogin);
+// });
+
+
+
+
+
+
+
+
+
+
+
+import { apiRequest } from "../../assets/js/api.js";
+import { ROUTES } from "../../assets/js/config.js";
+import { logout } from "../../assets/js/logout.js";
+
+const roles = [];
+let imageUrl = "";
+
+const els = {
+ form: document.getElementById("eventForm"),
+ roleInput: document.getElementById("roleInput"),
+ addRole: document.getElementById("addRole"),
+ rolesList: document.getElementById("rolesList"),
+ imageInput: document.getElementById("imageInput"),
+ imageBox: document.getElementById("imageBox"),
+ formMsg: document.getElementById("formMsg"),
+ saveDraft: document.getElementById("saveDraft"),
+ logoutBtn: document.getElementById("logoutBtn")
+};
+
+/* IMAGE */
+els.imageBox?.addEventListener("click", () => els.imageInput?.click());
+
+els.imageInput?.addEventListener("change", async (e) => {
+ const file = e.target.files?.[0];
+ if (!file) return;
+
+ // Temporary local preview
+ imageUrl = URL.createObjectURL(file);
+ els.imageBox.innerHTML = ` `;
+});
+
+/* ROLES */
+els.addRole?.addEventListener("click", () => {
+ const val = els.roleInput.value.trim();
+ if (!val) return;
+
+ if (!roles.includes(val)) {
+ roles.push(val);
+ }
+
+ els.roleInput.value = "";
+ renderRoles();
+});
+
+function renderRoles() {
+ els.rolesList.innerHTML = roles
+ .map((role, index) => `
+
+ ${role}
+ ×
+
+ `)
+ .join("");
+
+ document.querySelectorAll(".remove-role-btn").forEach((button) => {
+ button.addEventListener("click", () => {
+ const index = Number(button.dataset.index);
+ roles.splice(index, 1);
+ renderRoles();
+ });
+ });
+}
+
+function setMessage(message, type = "") {
+ if (!els.formMsg) return;
+ els.formMsg.textContent = message;
+ els.formMsg.className = "form-message";
+ if (type) {
+ els.formMsg.classList.add(type);
+ }
+}
+
+function getEventPayload() {
+ return {
+ name: document.getElementById("name")?.value.trim(),
+ category: document.getElementById("category")?.value.trim(),
+ description: document.getElementById("description")?.value.trim(),
+ location: {
+ venue: document.getElementById("venue")?.value.trim(),
+ city: document.getElementById("city")?.value.trim()
+ },
+ image: imageUrl,
+ date: document.getElementById("date")?.value,
+ startTime: document.getElementById("startTime")?.value.trim(),
+ endTime: document.getElementById("endTime")?.value.trim(),
+ volunteerSlots: Number(document.getElementById("slots")?.value || 0),
+ roles,
+ certificateEnabled: document.getElementById("certificateEnabled")?.checked || false,
+ requirements: document
+ .getElementById("requirements")
+ ?.value.split("\n")
+ .map((item) => item.trim())
+ .filter(Boolean) || []
+ };
+}
+
+function extractEventId(res) {
+ return (
+ res?._id ||
+ res?.id ||
+ res?.event?._id ||
+ res?.event?.id ||
+ res?.data?._id ||
+ res?.data?.id ||
+ ""
+ );
+}
+
+/* CREATE EVENT */
+async function createEvent(status = "draft") {
+ const payload = getEventPayload();
+
+ try {
+ setMessage(status === "published" ? "Creating and publishing event..." : "Saving draft...");
+
+ const res = await apiRequest("/events", {
+ method: "POST",
+ body: JSON.stringify(payload)
+ });
+
+ console.log("Create event response:", res);
+
+ const eventId = extractEventId(res);
+
+ if (!eventId) {
+ throw new Error("Event created but no event ID was returned by the backend.");
+ }
+
+ if (status === "published") {
+ await apiRequest(`/events/${eventId}/status`, {
+ method: "PATCH",
+ body: JSON.stringify({ status: "published" })
+ });
+ }
+
+ setMessage(
+ status === "published"
+ ? "Event published successfully!"
+ : "Draft saved successfully!",
+ "success"
+ );
+
+ setTimeout(() => {
+ window.location.href = ROUTES.organizerDashboard;
+ }, 800);
+ } catch (err) {
+ console.error("Create/publish event error:", err);
+ setMessage(err.message || "Something went wrong.", "error");
+ }
+}
+
+/* ACTIONS */
+els.form?.addEventListener("submit", (e) => {
+ e.preventDefault();
+ createEvent("published");
+});
+
+els.saveDraft?.addEventListener("click", (e) => {
+ e.preventDefault();
+ createEvent("draft");
+});
+
+/* LOGOUT */
+els.logoutBtn?.addEventListener("click", () => {
+ logout(ROUTES.organizerLogin);
+});
\ No newline at end of file
diff --git a/Organizer/events/event-details.css b/Organizer/events/event-details.css
new file mode 100644
index 0000000..ad6b3f4
--- /dev/null
+++ b/Organizer/events/event-details.css
@@ -0,0 +1,130 @@
+
+.top-actions {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.btn-primary {
+ background: #223f6b;
+ color: #fff;
+ text-decoration: none;
+ padding: 0.95rem 1.4rem;
+ border-radius: 0.75rem;
+ font-weight: 600;
+}
+
+
+.btn-danger {
+ background: #dc2626;
+ color: #fff;
+ text-decoration: none;
+ padding: 0.95rem 1.4rem;
+ border-radius: 0.75rem;
+ font-weight: 600;
+ cursor: pointer;
+}
+
+.icon-btn {
+ width: 2.6rem;
+ height: 2.6rem;
+ border: none;
+ border-radius: 50%;
+ background: transparent;
+ color: #223f6b;
+ font-size: 1.35rem;
+ cursor: pointer;
+}
+
+
+.profile-mini img {
+ width: 2.5rem;
+ height: 2.5rem;
+ border-radius: 50%;
+ object-fit: cover;
+}
+
+.event-details {
+ padding: 2rem;
+}
+
+.event-image {
+ position: relative;
+ margin: 1rem 0;
+}
+
+.event-image img {
+ width: 100%;
+ border-radius: 10px;
+}
+
+.status-badge {
+ position: absolute;
+ top: 15px;
+ right: 15px;
+ padding: 6px 14px;
+ border-radius: 6px;
+ color: #fff;
+ font-size: 0.85rem;
+}
+
+.status-published { background: green; }
+.status-draft { background: gray; }
+.status-cancelled { background: red; }
+
+.description-box {
+ border: 1px solid #ccc;
+ padding: 1rem;
+ border-radius: 8px;
+ margin: 1rem 0;
+}
+
+.event-meta {
+ display: flex;
+ gap: 2rem;
+ margin: 1rem 0;
+}
+
+.requirements ul {
+ padding-left: 1.5rem;
+}
+
+.stats {
+ display: flex;
+ gap: 2rem;
+ margin: 2rem 0;
+}
+
+.stat {
+ background: #f3f5f9;
+ padding: 1rem;
+ border-radius: 8px;
+ text-align: center;
+}
+
+.table-wrapper {
+ margin-top: 1.5rem;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+td, th {
+ padding: 0.8rem;
+ border-bottom: 1px solid #ddd;
+}
+
+.row-actions {
+ text-align: center;
+}
+
+.row-actions a,
+.row-actions button {
+ color: #223f6b;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ font-size: 1.2rem;
+}
\ No newline at end of file
diff --git a/Organizer/events/event-details.html b/Organizer/events/event-details.html
new file mode 100644
index 0000000..c2b7b4c
--- /dev/null
+++ b/Organizer/events/event-details.html
@@ -0,0 +1,151 @@
+
+
+
+
+
+ Event Details
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Loading...
+
+
+
+
Status
+
+
+ Events Description
+
+
+
+
+
+
+
+
+
+
+
+
Volunteer Requirements
+
+
+
+
+
+
+
+
+
+
+
+ Name
+ Email
+ Date
+ Status
+
+
+
+
+ Loading...
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Organizer/events/event-details.js b/Organizer/events/event-details.js
new file mode 100644
index 0000000..c318b56
--- /dev/null
+++ b/Organizer/events/event-details.js
@@ -0,0 +1,113 @@
+import { apiRequest } from "../../assets/js/api.js";
+import { requireRole } from "../../assets/js/auth.js";
+import { logout } from "../../assets/js/logout.js";
+
+const els = {
+ name: document.getElementById("eventName"),
+ image: document.getElementById("eventImage"),
+ description: document.getElementById("eventDescription"),
+ time: document.getElementById("eventTime"),
+ date: document.getElementById("eventDate"),
+ location: document.getElementById("eventLocation"),
+ requirements: document.getElementById("requirementsList"),
+ totalSlots: document.getElementById("totalSlots"),
+ registered: document.getElementById("registered"),
+ remaining: document.getElementById("remaining"),
+ table: document.getElementById("volunteerTable"),
+ statusBadge: document.getElementById("statusBadge"),
+ cancelBtn: document.getElementById("cancelBtn"),
+ editBtn: document.getElementById("editBtn"),
+ logoutBtn: document.getElementById("logoutBtn")
+};
+
+const eventId = new URLSearchParams(window.location.search).get("id");
+
+let eventData = null;
+
+function setStatus(status) {
+ els.statusBadge.textContent = status;
+ els.statusBadge.className = `status-badge status-${status}`;
+}
+
+async function loadEvent() {
+ const events = await apiRequest("/events");
+ eventData = events.find(e => e._id === eventId);
+
+ if (!eventData) {
+ els.name.textContent = "Event not found";
+ return;
+ }
+
+ els.name.textContent = eventData.name;
+ els.image.src = eventData.image;
+ els.description.textContent = eventData.description;
+
+ els.time.textContent = `${eventData.startTime} - ${eventData.endTime}`;
+ els.date.textContent = eventData.date;
+ els.location.textContent = eventData.location?.venue + ", " + eventData.location?.city;
+
+ setStatus(eventData.status);
+
+ // Requirements
+ els.requirements.innerHTML = eventData.requirements
+ .map(r => `${r} `)
+ .join("");
+
+ // Stats
+ els.totalSlots.textContent = eventData.volunteerSlots;
+
+ await loadVolunteers();
+}
+
+async function loadVolunteers() {
+ const data = await apiRequest(`/applications/events/${eventId}/registrations`);
+
+ const volunteers = Array.isArray(data) ? data : data.data || [];
+
+ els.registered.textContent = volunteers.length;
+ els.remaining.textContent = eventData.volunteerSlots - volunteers.length;
+
+ if (!volunteers.length) {
+ els.table.innerHTML = `No volunteers yet `;
+ return;
+ }
+
+ els.table.innerHTML = volunteers.map(v => `
+
+ ${v.user?.fullName || "Unknown"}
+ ${v.user?.email || "—"}
+ ${new Date(v.createdAt).toDateString()}
+ Confirmed
+
+ `).join("");
+}
+
+// Cancel event
+els.cancelBtn.addEventListener("click", async () => {
+ if (!confirm("Cancel this event?")) return;
+
+ await apiRequest(`/events/${eventId}/cancel`, {
+ method: "PATCH",
+ body: JSON.stringify({ reason: "Cancelled by organizer" })
+ });
+
+ alert("Event cancelled");
+ location.reload();
+});
+
+// Edit
+els.editBtn.addEventListener("click", () => {
+ window.location.href = `create-event.html?id=${eventId}`;
+});
+
+// Logout
+els.logoutBtn.addEventListener("click", () => {
+ logout("../login/organizer-login.html/");
+
+});
+
+// Init
+document.addEventListener("DOMContentLoaded", async () => {
+ await requireRole("organizer", "../login/organizer-login.html");
+ await loadEvent();
+});
diff --git a/Organizer/events/event-listing.css b/Organizer/events/event-listing.css
new file mode 100644
index 0000000..654fd13
--- /dev/null
+++ b/Organizer/events/event-listing.css
@@ -0,0 +1,249 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #f4f7fb;
+ color: #1f2f46;
+}
+
+.dashboard-layout {
+ display: flex;
+ min-height: 100vh;
+}
+
+.sidebar {
+ width: 15rem;
+ background: #1f3b63;
+ color: #fff;
+ padding: 1.5rem 1rem;
+ flex-shrink: 0;
+}
+
+.sidebar-logo {
+ text-align: center;
+ margin-bottom: 2rem;
+}
+
+.sidebar-logo img {
+ width: 6rem;
+}
+
+.sidebar-menu {
+ list-style: none;
+ display: flex;
+ flex-direction: column;
+ gap: 0.55rem;
+}
+
+.sidebar-menu li a,
+.sidebar-logout {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+ color: #fff;
+ text-decoration: none;
+ padding: 0.95rem 1rem;
+ border-radius: 0.65rem;
+ font-size: 0.98rem;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+}
+
+.sidebar-menu li.active a,
+.sidebar-menu li a:hover {
+ background: #1f80ea;
+}
+
+.sidebar-logout:hover {
+ background: #dc2626;
+}
+
+.main-content {
+ flex: 1;
+}
+
+.topbar {
+ background: #fff;
+ padding: 1.5rem 2rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+}
+
+.page-title {
+ font-size: 2rem;
+ font-weight: 700;
+ color: #223f6b;
+}
+
+.page-subtitle {
+ color: #6b7280;
+ font-size: 0.9rem;
+ margin-top: 0.3rem;
+}
+
+.topbar-actions {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.create-event-btn {
+ background: #223f6b;
+ color: #fff;
+ text-decoration: none;
+ padding: 0.95rem 1.4rem;
+ border-radius: 0.75rem;
+ font-weight: 600;
+}
+
+.icon-btn {
+ width: 2.6rem;
+ height: 2.6rem;
+ border: none;
+ border-radius: 50%;
+ background: transparent;
+ color: #223f6b;
+ font-size: 1.35rem;
+ cursor: pointer;
+}
+
+.profile-mini img {
+ width: 2.5rem;
+ height: 2.5rem;
+ border-radius: 50%;
+ object-fit: cover;
+}
+
+.events-section {
+ padding: 2rem;
+}
+
+.filter-tabs {
+ display: flex;
+ gap: 2rem;
+ margin-bottom: 1.5rem;
+ flex-wrap: wrap;
+}
+
+.filter-btn {
+ background: transparent;
+ border: none;
+ color: #111827;
+ font-size: 1rem;
+ cursor: pointer;
+ padding-bottom: 0.4rem;
+}
+
+.filter-btn.active {
+ color: #1f80ea;
+ font-weight: 600;
+}
+
+.table-wrapper {
+ background: #fff;
+ border-radius: 0.85rem;
+ overflow: hidden;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+thead {
+ background: #eef2f6;
+}
+
+th,
+td {
+ padding: 1rem;
+ text-align: left;
+ font-size: 0.92rem;
+}
+
+th {
+ color: #35527a;
+ font-weight: 600;
+}
+
+tbody tr {
+ border-bottom: 0.08rem solid #d7dde6;
+}
+
+tbody tr:last-child {
+ border-bottom: none;
+}
+
+.status-badge {
+ display: inline-block;
+ min-width: 7rem;
+ text-align: center;
+ padding: 0.62rem 1rem;
+ border-radius: 0.45rem;
+ color: #fff;
+ font-size: 0.88rem;
+ font-weight: 600;
+}
+
+.status-upcoming {
+ background: #f5ae42;
+}
+
+.status-published {
+ background: #26a65b;
+}
+
+.status-draft {
+ background: #6f809b;
+}
+
+.status-completed {
+ background: #1f80ea;
+}
+
+.status-cancelled {
+ background: #dc2626;
+}
+
+.row-actions {
+ text-align: center;
+}
+
+.row-actions a,
+.row-actions button {
+ color: #223f6b;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ font-size: 1.2rem;
+}
+
+@media (max-width: 52rem) {
+ .dashboard-layout {
+ flex-direction: column;
+ }
+
+ .sidebar {
+ width: 100%;
+ }
+
+ .topbar {
+ flex-direction: column;
+ gap: 1rem;
+ }
+
+ .table-wrapper {
+ overflow-x: auto;
+ }
+
+ table {
+ min-width: 50rem;
+ }
+}
diff --git a/Organizer/events/event-listing.html b/Organizer/events/event-listing.html
new file mode 100644
index 0000000..3760f04
--- /dev/null
+++ b/Organizer/events/event-listing.html
@@ -0,0 +1,110 @@
+
+
+
+
+
+ AIDLoop Organizer Events
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ All
+ Draft
+ Published
+ Cancelled
+
+
+
+
+
+
+ Events Name
+ Location
+ Date
+ Volunteers
+ Status
+
+
+
+
+
+
+ Loading events...
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Organizer/events/event-listing.js b/Organizer/events/event-listing.js
new file mode 100644
index 0000000..5946d06
--- /dev/null
+++ b/Organizer/events/event-listing.js
@@ -0,0 +1,46 @@
+import { apiRequest, normalizeArray } from "../../assets/js/api.js";
+import { requireOrganizer } from "../../assets/js/auth.js";
+import { logout } from "../../assets/js/logout.js";
+import { ROUTES } from "../../assets/js/config.js";
+import { formatDate, getLocationText } from "../../assets/js/utils.js";
+
+const table = document.getElementById("eventsTable");
+document.getElementById("logoutBtn").addEventListener("click", () => logout(ROUTES.home));
+
+function statusOf(event) {
+ const raw = String(event.status || "").toLowerCase();
+ if (raw === "cancelled" || raw === "canceled") return "cancelled";
+ if (raw === "draft") return "draft";
+ if (raw === "published" && event.date && new Date(event.date) < new Date()) return "completed";
+ return raw || "published";
+}
+
+document.addEventListener("DOMContentLoaded", async () => {
+ const organizer = await requireOrganizer();
+ if (!organizer) return;
+
+ try {
+ const payload = await apiRequest("/events");
+ const events = normalizeArray(payload, ["events"]);
+ const organizerId = String(organizer._id || organizer.id || "");
+ const own = events.filter((event) => {
+ if (typeof event.organizer === "object" && event.organizer) {
+ return String(event.organizer._id || event.organizer.id || "") === organizerId;
+ }
+ return String(event.organizerId || "") === organizerId;
+ });
+
+ table.innerHTML = own.map((event) => `
+
+ ${event.name || "Untitled Event"}
+ ${getLocationText(event)}
+ ${formatDate(event.date, "long")}
+ ${event.filledSlots ?? event.registrationsCount ?? 0}/${event.volunteerSlots ?? 0}
+ ${statusOf(event)}
+ Details
+
+ `).join("") || `No events found. `;
+ } catch {
+ table.innerHTML = `Failed to load events. `;
+ }
+});
diff --git a/Organizer/login/organizer-login.css b/Organizer/login/organizer-login.css
new file mode 100644
index 0000000..3d4b3b7
--- /dev/null
+++ b/Organizer/login/organizer-login.css
@@ -0,0 +1,190 @@
+Reset
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+/* Page */
+body {
+ background: #f5f7fa;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 100vh;
+}
+
+/* Container */
+.login-page {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ padding: 20px;
+}
+
+/* Card */
+.login-card {
+ background: #ffffff;
+ padding: 40px 35px;
+ border-radius: 18px;
+ width: 100%;
+ max-width: 500px;
+ text-align: center;
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.06);
+}
+
+/* Logo */
+.logo-wrap {
+ margin-bottom: 20px;
+}
+
+.logo {
+ width: 90px;
+ margin-bottom: 10px;
+}
+
+.logo-wrap p {
+ font-size: 14px;
+ color: #6b7280;
+}
+
+/* Title */
+.login-card h1 {
+ font-size: 24px;
+ font-weight: 600;
+ margin-bottom: 10px;
+ color: #111827;
+}
+
+.subtitle {
+ font-size: 14px;
+ color: #6b7280;
+ margin-bottom: 25px;
+ line-height: 1.5;
+}
+
+/* Form */
+.form-group {
+ text-align: left;
+ margin-bottom: 20px;
+}
+
+.form-group label {
+ font-size: 14px;
+ font-weight: 500;
+ margin-bottom: 6px;
+ display: block;
+ color: #374151;
+}
+
+/* Inputs */
+.form-group input {
+ width: 100%;
+ padding: 14px 16px;
+ border-radius: 10px;
+ border: 1px solid #d1d5db;
+ font-size: 14px;
+ outline: none;
+ transition: 0.2s ease;
+}
+
+.form-group input:focus {
+ border-color: #2563eb;
+ box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.1);
+}
+
+/* Password */
+.password-wrap {
+ position: relative;
+}
+
+.password-wrap input {
+ padding-right: 45px;
+}
+
+.password-wrap button {
+ position: absolute;
+ right: 12px;
+ top: 50%;
+ transform: translateY(-50%);
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ color: #6b7280;
+ font-size: 16px;
+}
+
+/* Button */
+.btn-primary {
+ width: 100%;
+ background: #1f3b63;
+ color: #fff;
+ padding: 15px;
+ border: none;
+ border-radius: 12px;
+ font-size: 15px;
+ font-weight: 500;
+ cursor: pointer;
+ margin-top: 10px;
+ transition: 0.3s;
+}
+
+.btn-primary:hover {
+ background: #173052;
+}
+
+/* Links */
+.links {
+ margin-top: 18px;
+ font-size: 13px;
+ color: #6b7280;
+ display: flex;
+ justify-content: center;
+ gap: 10px;
+}
+
+.links a {
+ text-decoration: none;
+ color: #2563eb;
+ font-weight: 500;
+}
+
+.links a:hover {
+ text-decoration: underline;
+}
+
+/* Bottom text */
+.register-text {
+ margin-top: 15px;
+ font-size: 13px;
+ color: #6b7280;
+}
+
+.register-text a {
+ color: #2563eb;
+ font-weight: 500;
+ text-decoration: none;
+}
+
+.register-text a:hover {
+ text-decoration: underline;
+}
+
+/* Errors */
+.error-message {
+ color: #dc2626;
+ font-size: 12px;
+ margin-top: 4px;
+}
+
+/* Responsive */
+@media (max-width: 480px) {
+ .login-card {
+ padding: 30px 20px;
+ }
+
+ .login-card h1 {
+ font-size: 20px;
+ }
+}
diff --git a/Organizer/login/organizer-login.html b/Organizer/login/organizer-login.html
new file mode 100644
index 0000000..56d1174
--- /dev/null
+++ b/Organizer/login/organizer-login.html
@@ -0,0 +1,81 @@
+
+
+
+
+
+ AIDLoop Organizer Login
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Connecting Volunteers to verified community events
+
+
+ Welcome back to AidLoop
+
+ Sign in to access your organizers dashboard, manage volunteer events, and track participant activity
+
+
+
+
+
+
+ Email Address
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Log in
+
+
+
+
+
+ Don't have an account?
+
+ Register Organization
+
+
+
+
+
+
+
+
+
+
diff --git a/Organizer/login/organizer-login.js b/Organizer/login/organizer-login.js
new file mode 100644
index 0000000..59c2685
--- /dev/null
+++ b/Organizer/login/organizer-login.js
@@ -0,0 +1,92 @@
+import { apiRequest } from "../../assets/js/api.js";
+import { ROUTES } from "../../assets/js/config.js";
+
+const els = {
+ form: document.getElementById("loginForm"),
+ email: document.getElementById("email"),
+ password: document.getElementById("password"),
+ togglePassword: document.getElementById("togglePassword"),
+ loginBtn: document.getElementById("loginBtn"),
+
+ emailError: document.getElementById("emailError"),
+ passwordError: document.getElementById("passwordError"),
+ formError: document.getElementById("formError"),
+ formSuccess: document.getElementById("formSuccess")
+};
+
+function clearMessages() {
+ els.emailError.textContent = "";
+ els.passwordError.textContent = "";
+ els.formError.textContent = "";
+ els.formSuccess.textContent = "";
+}
+
+function validateEmail(email) {
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
+}
+
+function setLoading(isLoading) {
+ els.loginBtn.disabled = isLoading;
+ els.loginBtn.textContent = isLoading ? "Logging in..." : "Log in";
+}
+
+/* Toggle password */
+els.togglePassword.addEventListener("click", () => {
+ const isPassword = els.password.type === "password";
+ els.password.type = isPassword ? "text" : "password";
+
+ els.togglePassword.innerHTML = isPassword
+ ? ' '
+ : ' ';
+});
+
+/* Submit */
+els.form.addEventListener("submit", async (e) => {
+ e.preventDefault();
+ clearMessages();
+
+ const email = els.email.value.trim();
+ const password = els.password.value.trim();
+
+ let valid = true;
+
+ if (!email) {
+ els.emailError.textContent = "Email is required";
+ valid = false;
+ } else if (!validateEmail(email)) {
+ els.emailError.textContent = "Invalid email format";
+ valid = false;
+ }
+
+ if (!password) {
+ els.passwordError.textContent = "Password is required";
+ valid = false;
+ }
+
+ if (!valid) return;
+
+ try {
+ setLoading(true);
+
+ const response = await apiRequest("/auth/webLogin", {
+ method: "POST",
+ body: JSON.stringify({ email, password })
+ });
+
+ els.formSuccess.textContent = response.message || "Login successful";
+
+ /* Optional: role check */
+ if (response?.user?.role !== "organizer") {
+ throw new Error("Not an organizer account");
+ }
+
+ setTimeout(() => {
+ window.location.href = ROUTES.organizerDashboard;
+ }, 800);
+
+ } catch (error) {
+ els.formError.textContent = error.message || "Login failed";
+ } finally {
+ setLoading(false);
+ }
+});
diff --git a/Organizer/profile/organizer-profile.css b/Organizer/profile/organizer-profile.css
new file mode 100644
index 0000000..9288c7a
--- /dev/null
+++ b/Organizer/profile/organizer-profile.css
@@ -0,0 +1,385 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #f4f7fb;
+ color: #1f2f46;
+}
+
+.dashboard-layout {
+ display: flex;
+ min-height: 100vh;
+}
+
+.sidebar {
+ width: 15rem;
+ background: #1f3b63;
+ color: #ffffff;
+ padding: 1.5rem 1rem;
+ flex-shrink: 0;
+}
+
+.sidebar-logo {
+ text-align: center;
+ margin-bottom: 2rem;
+}
+
+.sidebar-logo img {
+ width: 5.5rem;
+ max-width: 100%;
+}
+
+.sidebar-menu {
+ list-style: none;
+ display: flex;
+ flex-direction: column;
+ gap: 0.55rem;
+}
+
+.sidebar-menu li a,
+.sidebar-logout {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+ color: #ffffff;
+ text-decoration: none;
+ padding: 0.95rem 1rem;
+ border-radius: 0.65rem;
+ font-size: 0.96rem;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+}
+
+.sidebar-menu li.active a,
+.sidebar-menu li a:hover {
+ background: #1f80ea;
+}
+
+.sidebar-logout:hover {
+ background: #dc2626;
+}
+
+.main-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.topbar {
+ background: #ffffff;
+ padding: 2rem 2.2rem 1.2rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+}
+
+.page-title {
+ font-size: 2rem;
+ font-weight: 700;
+ color: #223f6b;
+}
+
+.page-subtitle {
+ margin-top: 0.3rem;
+ font-size: 0.9rem;
+ color: #6b7280;
+}
+
+.topbar-right {
+ display: flex;
+ align-items: center;
+ gap: 0.9rem;
+}
+
+.edit-profile-btn {
+ border: none;
+ background: #223f6b;
+ color: #ffffff;
+ padding: 0.95rem 1.45rem;
+ border-radius: 0.7rem;
+ font-size: 0.95rem;
+ font-weight: 600;
+ cursor: pointer;
+}
+
+.icon-btn {
+ width: 2.7rem;
+ height: 2.7rem;
+ border: none;
+ border-radius: 50%;
+ background: transparent;
+ color: #223f6b;
+ font-size: 1.35rem;
+ cursor: pointer;
+}
+
+.page-content {
+ padding: 2.2rem;
+}
+
+.profile-card,
+.about-card {
+ background: #ffffff;
+ border-radius: 1rem;
+ padding: 1.6rem;
+ margin-bottom: 2rem;
+}
+
+.profile-card {
+ max-width: 52rem;
+}
+
+.profile-main {
+ display: flex;
+ align-items: flex-start;
+ gap: 1.2rem;
+ margin-bottom: 1.5rem;
+}
+
+.profile-avatar {
+ width: 3.7rem;
+ height: 3.7rem;
+ border-radius: 0.7rem;
+ background: #223f6b;
+ color: #ffffff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.45rem;
+ font-weight: 700;
+ flex-shrink: 0;
+ text-transform: uppercase;
+}
+
+.profile-info h2 {
+ font-size: 2rem;
+ color: #223f6b;
+ font-weight: 700;
+}
+
+.org-type-line {
+ margin-top: 0.25rem;
+ color: #6b7280;
+ font-size: 0.95rem;
+ display: flex;
+ gap: 0.4rem;
+ align-items: center;
+ flex-wrap: wrap;
+}
+
+.dot {
+ color: #9ca3af;
+}
+
+.verified-badge {
+ margin-top: 1rem;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.45rem;
+ background: #edf8ef;
+ color: #2e7d32;
+ border: 0.08rem solid #b9dfbf;
+ padding: 0.45rem 0.8rem;
+ border-radius: 999rem;
+ font-size: 0.82rem;
+ font-weight: 600;
+}
+
+.verified-dot {
+ width: 0.45rem;
+ height: 0.45rem;
+ border-radius: 50%;
+ background: #35b24a;
+}
+
+.contact-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 1rem;
+}
+
+.info-chip {
+ background: #f4f6fa;
+ border-radius: 0.7rem;
+ padding: 0.95rem 1rem;
+ display: flex;
+ align-items: center;
+ gap: 0.65rem;
+}
+
+.info-chip i {
+ color: #223f6b;
+ width: 1rem;
+ flex-shrink: 0;
+}
+
+.info-chip input {
+ width: 100%;
+ border: none;
+ background: transparent;
+ outline: none;
+ color: #1f2f46;
+ font-size: 0.93rem;
+}
+
+.about-card {
+ max-width: 53rem;
+}
+
+.about-card h3 {
+ font-size: 1.8rem;
+ color: #223f6b;
+ margin-bottom: 1rem;
+}
+
+.about-box {
+ background: #d9e9fb;
+ border-radius: 0.9rem;
+ padding: 1rem;
+}
+
+.about-box textarea {
+ width: 100%;
+ min-height: 6rem;
+ border: none;
+ background: transparent;
+ resize: vertical;
+ outline: none;
+ color: #1f2f46;
+ font-size: 0.95rem;
+ line-height: 1.7;
+}
+
+.stats-row {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 1.8rem;
+ max-width: 54rem;
+}
+
+.mini-stat-card {
+ background: #ffffff;
+ border-radius: 0.9rem;
+ padding: 1.15rem;
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ box-shadow: 0 0.15rem 0.4rem rgba(0, 0, 0, 0.04);
+}
+
+.mini-stat-icon {
+ width: 3rem;
+ height: 3rem;
+ border-radius: 0.6rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 1.2rem;
+ flex-shrink: 0;
+}
+
+.mini-stat-icon.blue {
+ background: #dcecff;
+ color: #1f80ea;
+}
+
+.mini-stat-icon.green {
+ background: #dff2e4;
+ color: #2e8b57;
+}
+
+.mini-stat-icon.gold {
+ background: #fff0d8;
+ color: #d29a1f;
+}
+
+.mini-stat-text {
+ display: flex;
+ flex-direction: column;
+ gap: 0.35rem;
+}
+
+.mini-stat-label {
+ font-size: 0.72rem;
+ color: #8a94a6;
+ font-weight: 600;
+}
+
+.mini-stat-text h4 {
+ font-size: 1.8rem;
+ color: #223f6b;
+ font-weight: 700;
+}
+
+.mini-badge {
+ display: inline-block;
+ width: fit-content;
+ padding: 0.35rem 0.55rem;
+ border-radius: 0.45rem;
+ font-size: 0.72rem;
+ font-weight: 600;
+}
+
+.mini-badge.blue {
+ background: #dcecff;
+ color: #1f80ea;
+}
+
+.mini-badge.green {
+ background: #dff2e4;
+ color: #2e8b57;
+}
+
+.mini-badge.gold {
+ background: #fff0d8;
+ color: #d29a1f;
+}
+
+.profile-message {
+ margin-top: 1rem;
+ font-size: 0.92rem;
+ font-weight: 500;
+}
+
+.profile-message.success {
+ color: #15803d;
+}
+
+.profile-message.error {
+ color: #dc2626;
+}
+
+@media (max-width: 68rem) {
+ .stats-row {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 64rem) {
+ .dashboard-layout {
+ flex-direction: column;
+ }
+
+ .sidebar {
+ width: 100%;
+ }
+
+ .topbar {
+ flex-direction: column;
+ gap: 1rem;
+ }
+
+ .contact-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .profile-main {
+ flex-direction: column;
+ }
+}
diff --git a/Organizer/profile/organizer-profile.html b/Organizer/profile/organizer-profile.html
new file mode 100644
index 0000000..73cb2a2
--- /dev/null
+++ b/Organizer/profile/organizer-profile.html
@@ -0,0 +1,186 @@
+
+
+
+
+
+ AIDLoop Organization Profile
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
AL
+
+
+
Loading...
+
+ Organization
+ •
+ Volunteer Management
+
+
+
+
+ Verified Org
+
+
+
+
+
+
+
+
+ About Organization
+
+
+
+
+
+
+
+
+
+
+
+
TOTAL EVENTS CREATED
+
0
+
This month
+
+
+
+
+
+
+
+
+
TOTAL VOLUNTEERS
+
0
+
This month
+
+
+
+
+
+
+
+
+
CERTIFICATES ISSUED
+
0
+
This month
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Organizer/profile/organizer-profile.js b/Organizer/profile/organizer-profile.js
new file mode 100644
index 0000000..f4d2f01
--- /dev/null
+++ b/Organizer/profile/organizer-profile.js
@@ -0,0 +1,196 @@
+import { apiRequest, normalizeArray } from "../../assets/js/api.js";
+import { requireRole } from ".../../assets/js/auth.js";
+import { logout } from "../../assets/js/logout.js";
+import { ROUTES } from "../../assets/js/config.js";
+
+const els = {
+ orgName: document.getElementById("orgName"),
+ orgType: document.getElementById("orgType"),
+ orgCategory: document.getElementById("orgCategory"),
+ verificationText: document.getElementById("verificationText"),
+ profileAvatarBox: document.getElementById("profileAvatarBox"),
+
+ email: document.getElementById("email"),
+ phoneNumber: document.getElementById("phoneNumber"),
+ website: document.getElementById("website"),
+ location: document.getElementById("location"),
+ description: document.getElementById("description"),
+
+ totalEvents: document.getElementById("totalEvents"),
+ totalVolunteers: document.getElementById("totalVolunteers"),
+ certificatesIssued: document.getElementById("certificatesIssued"),
+
+ eventsMonthText: document.getElementById("eventsMonthText"),
+ volunteersMonthText: document.getElementById("volunteersMonthText"),
+ certificatesMonthText: document.getElementById("certificatesMonthText"),
+
+ editProfileBtn: document.getElementById("editProfileBtn"),
+ logoutBtn: document.getElementById("logoutBtn"),
+ profileMessage: document.getElementById("profileMessage")
+};
+
+let organizer = null;
+let isEditing = false;
+
+function getInitials(name) {
+ return String(name || "AL")
+ .split(" ")
+ .slice(0, 2)
+ .map((part) => part.charAt(0))
+ .join("")
+ .toUpperCase();
+}
+
+function getVerificationLabel(user) {
+ const status = String(user.status || "").toLowerCase();
+ const approvalStatus = String(user.approvalStatus || "").toLowerCase();
+ const isVerified = Boolean(user.isVerified);
+
+ if (
+ status === "verified" ||
+ status === "approved" ||
+ approvalStatus === "verified" ||
+ approvalStatus === "approved" ||
+ isVerified
+ ) {
+ return "Verified Org";
+ }
+
+ return "Pending Verification";
+}
+
+function getLocationText(user) {
+ if (typeof user.location === "string" && user.location.trim()) {
+ return user.location;
+ }
+
+ if (user.location && typeof user.location === "object") {
+ return [
+ user.location.venue,
+ user.location.city || user.location.state
+ ].filter(Boolean).join(", ");
+ }
+
+ return user.city || user.state || "—";
+}
+
+function populateProfile(user) {
+ const name = user.fullName || user.name || user.organizationName || "Organization";
+
+ els.orgName.textContent = name;
+ els.orgType.textContent = user.organizationType || "Non-profit";
+ els.orgCategory.textContent = user.category || "Volunteer Management";
+ els.verificationText.textContent = getVerificationLabel(user);
+ els.profileAvatarBox.textContent = getInitials(name);
+
+ els.email.value = user.email || "";
+ els.phoneNumber.value = user.phoneNumber || user.phone || "";
+ els.website.value =
+ user.website ||
+ user.socialLink ||
+ user.socialLinks?.[0] ||
+ "—";
+ els.location.value = getLocationText(user) || "—";
+ els.description.value =
+ user.description ||
+ user.bio ||
+ "No organization description available.";
+}
+
+function setEditable(editable) {
+ els.phoneNumber.readOnly = !editable;
+ els.website.readOnly = !editable;
+ els.location.readOnly = !editable;
+ els.description.readOnly = !editable;
+}
+
+async function loadStats() {
+ try {
+ const eventsPayload = await apiRequest("/events");
+ const events = normalizeArray(eventsPayload, ["events"]);
+
+ const organizerId = String(organizer._id || organizer.id || "");
+
+ const ownEvents = events.filter((event) => {
+ if (typeof event.organizer === "object" && event.organizer) {
+ return String(event.organizer._id || event.organizer.id || "") === organizerId;
+ }
+ return String(event.organizerId || "") === organizerId;
+ });
+
+ const totalEvents = ownEvents.length;
+
+ const totalVolunteers = ownEvents.reduce((sum, event) => {
+ return sum + (
+ event.filledSlots ??
+ event.registrationsCount ??
+ event.registeredCount ??
+ event.attendeesCount ??
+ 0
+ );
+ }, 0);
+
+ els.totalEvents.textContent = totalEvents;
+ els.totalVolunteers.textContent = totalVolunteers;
+ els.certificatesIssued.textContent = "0";
+
+ els.eventsMonthText.textContent = "This month";
+ els.volunteersMonthText.textContent = "This month";
+ els.certificatesMonthText.textContent = "This month";
+ } catch {
+ els.totalEvents.textContent = "0";
+ els.totalVolunteers.textContent = "0";
+ els.certificatesIssued.textContent = "0";
+ }
+}
+
+async function saveProfile() {
+ const payload = {
+ phoneNumber: els.phoneNumber.value.trim(),
+ website: els.website.value.trim(),
+ location: els.location.value.trim(),
+ description: els.description.value.trim()
+ };
+
+ await apiRequest("/user/me", {
+ method: "PUT",
+ body: JSON.stringify(payload)
+ });
+}
+
+els.editProfileBtn.addEventListener("click", async () => {
+ els.profileMessage.textContent = "";
+ els.profileMessage.className = "profile-message";
+
+ if (!isEditing) {
+ isEditing = true;
+ setEditable(true);
+ els.editProfileBtn.textContent = "Save Profile";
+ return;
+ }
+
+ try {
+ await saveProfile();
+ isEditing = false;
+ setEditable(false);
+ els.editProfileBtn.textContent = "Edit Profile";
+ els.profileMessage.textContent = "Profile updated successfully.";
+ els.profileMessage.classList.add("success");
+ } catch (error) {
+ els.profileMessage.textContent = error.message || "Failed to update profile.";
+ els.profileMessage.classList.add("error");
+ }
+});
+
+els.logoutBtn.addEventListener("click", () => {
+ logout(ROUTES.organizerLogin);
+});
+
+document.addEventListener("DOMContentLoaded", async () => {
+ organizer = await requireRole("organizer", ROUTES.organizerLogin);
+ if (!organizer) return;
+
+ populateProfile(organizer);
+ setEditable(false);
+ await loadStats();
+});
diff --git a/Organizer/signup/organizer-signup.css b/Organizer/signup/organizer-signup.css
new file mode 100644
index 0000000..03d14e4
--- /dev/null
+++ b/Organizer/signup/organizer-signup.css
@@ -0,0 +1,111 @@
+body {
+ background: #f5f7fa;
+ font-family: "Poppins", sans-serif;
+}
+
+.signup-page {
+ display: flex;
+ justify-content: center;
+ padding: 40px 20px;
+}
+
+
+.signup-card {
+ background: #fff;
+ width: 100%;
+ max-width: 520px;
+ padding: 40px;
+ border-radius: 18px;
+ box-shadow: 0 10px 40px rgba(0,0,0,0.05);
+}
+
+
+.logo-wrap {
+ text-align: center;
+ margin-bottom: 20px;
+}
+
+.logo {
+ width: 80px;
+}
+
+.logo-wrap h2 {
+ margin-top: 10px;
+ font-size: 22px;
+}
+
+.logo-wrap p {
+ font-size: 13px;
+ color: #6b7280;
+}
+
+
+.section-title {
+ background: #6b7a90;
+ color: #fff;
+ padding: 10px;
+ border-radius: 8px;
+ font-size: 13px;
+ margin: 20px 0 10px;
+ text-align: center;
+}
+
+
+.form-group {
+ margin-bottom: 15px;
+}
+
+label {
+ font-size: 13px;
+ color: #374151;
+}
+
+input,
+textarea {
+ width: 100%;
+ padding: 12px;
+ border-radius: 8px;
+ border: 1px solid #d1d5db;
+ margin-top: 5px;
+}
+
+textarea {
+ min-height: 80px;
+}
+
+.row {
+ display: flex;
+ gap: 10px;
+}
+
+.row input {
+ flex: 1;
+}
+
+
+.btn-primary {
+ width: 100%;
+ padding: 14px;
+ border-radius: 10px;
+ border: none;
+ background: #1f3b63;
+ color: #fff;
+ margin-top: 20px;
+ cursor: pointer;
+}
+
+.login-link {
+ text-align: center;
+ margin-top: 15px;
+ font-size: 13px;
+}
+
+.login-link a {
+ color: #2563eb;
+}
+
+
+
+
+
+
diff --git a/Organizer/signup/organizer-signup.html b/Organizer/signup/organizer-signup.html
new file mode 100644
index 0000000..7bb090c
--- /dev/null
+++ b/Organizer/signup/organizer-signup.html
@@ -0,0 +1,90 @@
+
+
+
+
+
+ AIDLoop Organizer Signup
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Organizer/signup/organizer-signup.js b/Organizer/signup/organizer-signup.js
new file mode 100644
index 0000000..abc9a69
--- /dev/null
+++ b/Organizer/signup/organizer-signup.js
@@ -0,0 +1,80 @@
+import { apiRequest } from "../../assets/js/api.js";
+import { ROUTES } from "../../assets/js/config.js";
+
+const els = {
+ form: document.getElementById("signupForm"),
+ name: document.getElementById("name"),
+ email: document.getElementById("email"),
+ password: document.getElementById("password"),
+ phone: document.getElementById("phone"),
+ state: document.getElementById("state"),
+ city: document.getElementById("city"),
+ description: document.getElementById("description"),
+ social: document.getElementById("social"),
+ btn: document.getElementById("signupBtn"),
+ error: document.getElementById("formError"),
+ success: document.getElementById("formSuccess")
+};
+
+function setLoading(isLoading) {
+ els.btn.disabled = isLoading;
+ els.btn.textContent = isLoading ? "Creating account..." : "Sign Up";
+}
+
+function validateEmail(email) {
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
+}
+
+els.form.addEventListener("submit", async (e) => {
+ e.preventDefault();
+
+ els.error.textContent = "";
+ els.success.textContent = "";
+
+ const fullName = els.name.value.trim();
+ const email = els.email.value.trim();
+ const password = els.password.value.trim();
+
+ if (!fullName || !email || !password) {
+ els.error.textContent = "All required fields must be filled";
+ return;
+ }
+
+ if (!validateEmail(email)) {
+ els.error.textContent = "Invalid email address";
+ return;
+ }
+
+ if (password.length < 6) {
+ els.error.textContent = "Password must be at least 6 characters";
+ return;
+ }
+
+ try {
+ setLoading(true);
+
+ const response = await apiRequest("/auth/register/web", {
+ method: "POST",
+ header: {"Content-Type": "application.json"},
+ body: JSON.stringify({
+ fullName,
+ email,
+ password
+ })
+ });
+
+ els.success.textContent =
+ response.message || "Account created successfully. Check your email for verification.";
+
+ sessionStorage.setItem("aidloop_pending_verification_email", email);
+ localStorage.setItem("aidloop_organizer_email", email);
+
+ setTimeout(() => {
+ window.location.href = "../verify-email/verify-email.html";
+ }, 1200);
+ } catch (error) {
+ els.error.textContent = error.message || "Signup failed";
+ } finally {
+ setLoading(false);
+ }
+});
diff --git a/Organizer/verify-email/verify-email.css b/Organizer/verify-email/verify-email.css
new file mode 100644
index 0000000..16932d4
--- /dev/null
+++ b/Organizer/verify-email/verify-email.css
@@ -0,0 +1,123 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #f5f7fa;
+ min-height: 100vh;
+}
+
+.verify-page {
+ min-height: 100vh;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 2rem;
+}
+
+.verify-card {
+ width: 100%;
+ max-width: 40rem;
+ min-height: 46rem;
+ background: #ffffff;
+ padding: 4rem 2rem;
+ text-align: center;
+ box-shadow: 0 0.625rem 2.5rem rgba(0, 0, 0, 0.05);
+}
+
+.icon-wrap {
+ display: flex;
+ justify-content: center;
+ margin-bottom: 1.5rem;
+}
+
+.mail-box {
+ width: 7rem;
+ height: 7rem;
+ border-radius: 1rem;
+ background: #f5ae42;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.mail-box i {
+ font-size: 4rem;
+ color: #ffffff;
+}
+
+.verify-card h1 {
+ font-size: 2rem;
+ color: #223f6b;
+ margin-bottom: 3rem;
+ font-weight: 700;
+}
+
+.divider {
+ width: 100%;
+ height: 0.0625rem;
+ background: #cfd5dc;
+ margin-bottom: 1.5rem;
+}
+
+.message {
+ max-width: 25rem;
+ margin: 0 auto 0.75rem;
+ font-size: 1rem;
+ line-height: 1.7;
+ color: #4b5563;
+}
+
+.email-text {
+ margin-bottom: 1.5rem;
+ font-size: 0.95rem;
+ color: #223f6b;
+ font-weight: 600;
+}
+
+.primary-btn {
+ margin-top: 2rem;
+ width: 100%;
+ max-width: 15rem;
+ border: none;
+ background: #223f6b;
+ color: #ffffff;
+ padding: 1rem 1.25rem;
+ border-radius: 0.75rem;
+ font-size: 1rem;
+ font-weight: 600;
+ cursor: pointer;
+}
+
+.primary-btn:hover {
+ background: goldenrod;
+}
+
+.primary-btn:disabled {
+ background: #dc2626;
+ cursor: not-allowed;
+}
+
+@media (max-width: 40rem) {
+ .verify-card {
+ min-height: auto;
+ padding: 3rem 1.25rem;
+ }
+
+ .verify-card h1 {
+ font-size: 1.7rem;
+ margin-bottom: 2rem;
+ }
+
+ .mail-box {
+ width: 6rem;
+ height: 6rem;
+ }
+
+ .mail-box i {
+ font-size: 3.2rem;
+ }
+}
diff --git a/Organizer/verify-email/verify-email.html b/Organizer/verify-email/verify-email.html
new file mode 100644
index 0000000..b2a10a6
--- /dev/null
+++ b/Organizer/verify-email/verify-email.html
@@ -0,0 +1,44 @@
+
+
+
+
+
+ Verify Your Email
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Verify Your Email
+
+
+
+
+ We've sent a verification link to your email address.
+ Please check your inbox and click the link to continue.
+
+
+
+
+
+
+
+ Resend Email
+
+
+
+
+
+
diff --git a/Organizer/verify-email/verify-email.js b/Organizer/verify-email/verify-email.js
new file mode 100644
index 0000000..dd6306c
--- /dev/null
+++ b/Organizer/verify-email/verify-email.js
@@ -0,0 +1,70 @@
+import { apiRequest } from "../../assets/js/api.js";
+import { ROUTES } from "../../assets/js/config.js";
+
+const els = {
+ resendBtn: document.getElementById("resendBtn"),
+ formError: document.getElementById("formError"),
+ formSuccess: document.getElementById("formSuccess"),
+ emailText: document.getElementById("emailText")
+};
+
+function getStoredEmail() {
+ return (
+ sessionStorage.getItem("aidloop_pending_verification_email") ||
+ localStorage.getItem("aidloop_organizer_email") ||
+ ""
+ );
+}
+
+function setLoading(isLoading) {
+ els.resendBtn.disabled = isLoading;
+ els.resendBtn.textContent = isLoading ? "Resending..." : "Resend Email";
+}
+
+async function resendVerification() {
+ const email = getStoredEmail();
+
+ els.formError.textContent = "";
+ els.formSuccess.textContent = "";
+
+ if (!email) {
+ els.formError.textContent = "No email found. Please sign up again.";
+ return;
+ }
+
+ try {
+ setLoading(true);
+
+ const result = await apiRequest("/auth/resend-otp", {
+ method: "POST",
+ body: JSON.stringify({ email })
+ });
+
+ els.formSuccess.textContent =
+ result.message || "Verification email sent successfully.";
+ } catch (error) {
+ els.formError.textContent = error.message || "Failed to resend verification email.";
+ } finally {
+ setLoading(false);
+ }
+}
+
+document.addEventListener("DOMContentLoaded", () => {
+ const email = getStoredEmail();
+
+ if (email) {
+ els.emailText.textContent = `Sent to: ${email}`;
+ } else {
+ els.emailText.textContent = "";
+ }
+
+ els.resendBtn.addEventListener("click", resendVerification);
+});
+
+
+
+
+
+
+
+
diff --git a/Organizer/volunteers/volunteers.css b/Organizer/volunteers/volunteers.css
new file mode 100644
index 0000000..b045df3
--- /dev/null
+++ b/Organizer/volunteers/volunteers.css
@@ -0,0 +1,406 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+body {
+ background: #f4f7fb;
+ color: #1f2f46;
+}
+
+.dashboard-layout {
+ display: flex;
+ min-height: 100vh;
+}
+
+.sidebar {
+ width: 15rem;
+ background: #1f3b63;
+ color: #ffffff;
+ padding: 1.5rem 1rem;
+ flex-shrink: 0;
+}
+
+.sidebar-logo {
+ text-align: center;
+ margin-bottom: 2rem;
+}
+
+.sidebar-logo img {
+ width: 5.5rem;
+ max-width: 100%;
+}
+
+.sidebar-menu {
+ list-style: none;
+ display: flex;
+ flex-direction: column;
+ gap: 0.55rem;
+}
+
+.sidebar-menu li a,
+.sidebar-logout {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+ color: #ffffff;
+ text-decoration: none;
+ padding: 0.95rem 1rem;
+ border-radius: 0.65rem;
+ font-size: 0.96rem;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+}
+
+.sidebar-menu li.active a,
+.sidebar-menu li a:hover {
+ background: #1f80ea;
+}
+
+.sidebar-logout:hover {
+ background: #dc2626;
+}
+
+.main-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.topbar {
+ background: #ffffff;
+ padding: 2rem 2.2rem 1.2rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+}
+
+.page-title {
+ font-size: 2rem;
+ font-weight: 700;
+ color: #223f6b;
+}
+
+.page-subtitle {
+ margin-top: 0.3rem;
+ font-size: 0.9rem;
+ color: #6b7280;
+}
+
+.topbar-right {
+ display: flex;
+ align-items: center;
+ gap: 0.9rem;
+}
+
+.icon-btn {
+ width: 2.7rem;
+ height: 2.7rem;
+ border: none;
+ border-radius: 50%;
+ background: transparent;
+ color: #223f6b;
+ font-size: 1.35rem;
+ cursor: pointer;
+}
+
+.page-content {
+ padding: 2rem 2.2rem;
+}
+
+.toolbar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 1rem;
+ flex-wrap: wrap;
+ margin-bottom: 2rem;
+}
+
+.filter-tabs {
+ display: flex;
+ align-items: center;
+ gap: 2rem;
+ flex-wrap: wrap;
+}
+
+.filter-btn {
+ border: none;
+ background: transparent;
+ color: #111827;
+ font-size: 1rem;
+ cursor: pointer;
+ padding-bottom: 0.35rem;
+}
+
+.filter-btn.active {
+ color: #1f80ea;
+ font-weight: 600;
+}
+
+.search-box {
+ width: 100%;
+ max-width: 17rem;
+ display: flex;
+ align-items: center;
+ border: 0.08rem solid #8fa1ba;
+ border-radius: 0.55rem;
+ overflow: hidden;
+ background: #ffffff;
+}
+
+.search-box input {
+ flex: 1;
+ border: none;
+ outline: none;
+ padding: 0.9rem 0.9rem;
+ font-size: 0.9rem;
+ background: transparent;
+}
+
+.search-box button {
+ width: 3rem;
+ border: none;
+ background: transparent;
+ color: #1f80ea;
+ cursor: pointer;
+ font-size: 1rem;
+}
+
+.stats-grid {
+ display: grid;
+ gap: 2rem;
+ margin-bottom: 2rem;
+}
+
+.three-cards {
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ max-width: 48rem;
+}
+
+.stat-card {
+ background: #ffffff;
+ border: 0.08rem solid #9ca9bb;
+ border-radius: 0.85rem;
+ overflow: hidden;
+}
+
+.stat-number {
+ text-align: center;
+ font-size: 2.2rem;
+ font-weight: 700;
+ color: #111111;
+ padding: 1rem 0 0.7rem;
+}
+
+.stat-footer {
+ border-top: 0.08rem solid #c9d2dd;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 0.55rem;
+ padding: 0.95rem 1rem;
+ color: #526176;
+ font-size: 0.86rem;
+}
+
+.stat-footer i {
+ color: #1f80ea;
+ font-size: 1rem;
+}
+
+.table-wrapper {
+ background: #ffffff;
+ border-radius: 0.85rem;
+ overflow: hidden;
+ border: 0.08rem solid #d8dfe8;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+thead {
+ background: #eef2f6;
+}
+
+th,
+td {
+ padding: 1rem;
+ text-align: left;
+ font-size: 0.92rem;
+}
+
+th {
+ color: #35527a;
+ font-weight: 600;
+}
+
+tbody tr {
+ border-bottom: 0.08rem solid #d7dde6;
+}
+
+tbody tr:last-child {
+ border-bottom: none;
+}
+
+.volunteer-name {
+ display: flex;
+ align-items: center;
+ gap: 0.8rem;
+}
+
+.avatar {
+ width: 2.1rem;
+ height: 2.1rem;
+ border-radius: 50%;
+ object-fit: cover;
+ flex-shrink: 0;
+}
+
+.badge {
+ display: inline-block;
+ min-width: 6.8rem;
+ text-align: center;
+ padding: 0.55rem 0.9rem;
+ border-radius: 0.4rem;
+ color: #ffffff;
+ font-size: 0.84rem;
+ font-weight: 600;
+}
+
+.confirmed {
+ background: #26a65b;
+}
+
+.attendance-wrap {
+ display: flex;
+ align-items: center;
+}
+
+.attendance-box {
+ width: 1.8rem;
+ height: 1.8rem;
+ border: 0.1rem solid #1f80ea;
+ border-radius: 0.45rem;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ background: #ffffff;
+ cursor: pointer;
+ transition: 0.2s ease;
+}
+
+.attendance-box.checked {
+ background: #1f80ea;
+ color: #ffffff;
+}
+
+.attendance-box:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+}
+
+.row-actions {
+ text-align: center;
+ color: #223f6b;
+ font-size: 1.1rem;
+}
+
+.row-actions button {
+ border: none;
+ background: transparent;
+ color: inherit;
+ cursor: pointer;
+}
+
+.table-footer {
+ margin-top: 1rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 1rem;
+ flex-wrap: wrap;
+ color: #4b5563;
+ font-size: 0.9rem;
+}
+
+.pagination {
+ display: flex;
+ align-items: center;
+ gap: 0.35rem;
+ flex-wrap: wrap;
+}
+
+.page-btn {
+ border: 0.08rem solid #d6dde6;
+ background: #ffffff;
+ color: #1f2f46;
+ padding: 0.42rem 0.75rem;
+ border-radius: 0.3rem;
+ cursor: pointer;
+ font-size: 0.8rem;
+}
+
+.page-btn.active {
+ background: #1f80ea;
+ color: #ffffff;
+ border-color: #1f80ea;
+}
+
+.qualification-cell {
+ font-size: 0.84rem;
+ font-weight: 500;
+}
+
+.qualification-cell.qualified {
+ color: #15803d;
+}
+
+.qualification-cell.pending {
+ color: #9ca3af;
+}
+
+@media (max-width: 68rem) {
+ .three-cards {
+ grid-template-columns: 1fr;
+ max-width: 100%;
+ }
+}
+
+@media (max-width: 64rem) {
+ .dashboard-layout {
+ flex-direction: column;
+ }
+
+ .sidebar {
+ width: 100%;
+ }
+
+ .topbar {
+ flex-direction: column;
+ gap: 1rem;
+ }
+
+ .toolbar {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .search-box {
+ max-width: 100%;
+ }
+
+ .table-wrapper {
+ overflow-x: auto;
+ }
+
+ table {
+ min-width: 46rem;
+ }
+}
\ No newline at end of file
diff --git a/Organizer/volunteers/volunteers.html b/Organizer/volunteers/volunteers.html
new file mode 100644
index 0000000..49d5605
--- /dev/null
+++ b/Organizer/volunteers/volunteers.html
@@ -0,0 +1,178 @@
+
+
+
+
+
+ AIDLoop Volunteers
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Name
+ Email
+ Status
+ Attendance
+ Certificate
+
+
+
+
+
+
+ Loading volunteers...
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Organizer/volunteers/volunteers.js b/Organizer/volunteers/volunteers.js
new file mode 100644
index 0000000..41963ce
--- /dev/null
+++ b/Organizer/volunteers/volunteers.js
@@ -0,0 +1,202 @@
+import { apiRequest } from "../../assets/js/api.js";
+import { requireRole } from "../../assets/js/auth.js";
+import { logout } from "../../assets/js/logout.js";
+import { ROUTES } from "../../assets/js/config.js";
+
+const eventId = new URLSearchParams(window.location.search).get("eventId");
+
+const els = {
+ table: document.getElementById("volunteerTable"),
+ total: document.getElementById("totalRegistered"),
+ attended: document.getElementById("attendedCount"),
+ noShow: document.getElementById("noShowCount"),
+ searchInput: document.getElementById("searchInput"),
+ tableCountText: document.getElementById("tableCountText"),
+ filterBtns: document.querySelectorAll(".filter-btn"),
+ logoutBtn: document.getElementById("logoutBtn")
+};
+
+let allVolunteers = [];
+let currentFilter = "all";
+
+function getStatus(v) {
+ return String(v.status || "confirmed").toLowerCase();
+}
+
+function getAttendance(v) {
+ return String(v.attendance || "").toLowerCase();
+}
+
+function getDisplayName(v) {
+ return v.user?.fullName || "Unknown";
+}
+
+function getEmail(v) {
+ return v.user?.email || "—";
+}
+
+function getAvatar(v) {
+ return v.user?.profileImage || "https://i.pravatar.cc/100?img=12";
+}
+
+function getQualification(v) {
+ return getAttendance(v) === "attended" ? "Qualified for certificate" : "Pending attendance";
+}
+
+function renderStats() {
+ const total = allVolunteers.length;
+ const attended = allVolunteers.filter((v) => getAttendance(v) === "attended").length;
+ const noShow = Math.max(0, total - attended);
+
+ els.total.textContent = total;
+ els.attended.textContent = attended;
+ els.noShow.textContent = noShow;
+}
+
+function renderTable() {
+ const query = els.searchInput.value.trim().toLowerCase();
+
+ let filtered = [...allVolunteers];
+
+ if (currentFilter !== "all") {
+ filtered = filtered.filter((v) => getStatus(v) === currentFilter);
+ }
+
+ if (query) {
+ filtered = filtered.filter((v) => {
+ const searchable = `${getDisplayName(v)} ${getEmail(v)} ${getQualification(v)}`.toLowerCase();
+ return searchable.includes(query);
+ });
+ }
+
+ els.tableCountText.textContent = `Showing ${filtered.length} of ${allVolunteers.length} entries`;
+
+ if (!filtered.length) {
+ els.table.innerHTML = `
+ No volunteers found
+ `;
+ return;
+ }
+
+ els.table.innerHTML = filtered.map((v) => {
+ const id = v._id;
+ const attended = getAttendance(v) === "attended";
+
+ return `
+
+
+
+
+
${getDisplayName(v)}
+
+
+ ${getEmail(v)}
+ Confirmed
+
+
+
+ ${attended ? ' ' : ""}
+
+
+
+
+ ${attended ? "Qualified for certificate" : "Pending attendance"}
+
+
+
+
+
+
+
+ `;
+ }).join("");
+
+ attachAttendanceHandlers();
+}
+
+function attachAttendanceHandlers() {
+ document.querySelectorAll(".attendance-box").forEach((btn) => {
+ btn.addEventListener("click", async () => {
+ const id = btn.dataset.id;
+ const targetVolunteer = allVolunteers.find((v) => v._id === id);
+ const alreadyAttended = getAttendance(targetVolunteer) === "attended";
+
+ if (alreadyAttended) return;
+
+ try {
+ btn.disabled = true;
+
+ await apiRequest(`/applications/registrations/${id}/attendance`, {
+ method: "PATCH",
+ body: JSON.stringify({ status: "attended" })
+ });
+
+ allVolunteers = allVolunteers.map((v) =>
+ v._id === id
+ ? {
+ ...v,
+ attendance: "attended",
+ certificateQualified: true
+ }
+ : v
+ );
+
+ renderStats();
+ renderTable();
+ } catch (err) {
+ alert(err.message || "Failed to update attendance");
+ btn.disabled = false;
+ }
+ });
+ });
+}
+
+async function loadVolunteers() {
+ const data = await apiRequest(`/applications/events/${eventId}/registrations`);
+ const volunteers = Array.isArray(data) ? data : data.data || [];
+ allVolunteers = volunteers;
+
+ renderStats();
+ renderTable();
+}
+
+function bindFilters() {
+ els.filterBtns.forEach((btn) => {
+ btn.addEventListener("click", () => {
+ els.filterBtns.forEach((b) => b.classList.remove("active"));
+ btn.classList.add("active");
+ currentFilter = btn.dataset.filter;
+ renderTable();
+ });
+ });
+}
+
+els.searchInput.addEventListener("input", renderTable);
+
+els.logoutBtn.addEventListener("click", () => {
+ logout(ROUTES.organizerLogin);
+});
+
+document.addEventListener("DOMContentLoaded", async () => {
+ await requireRole("organizer", ROUTES.organizerLogin);
+
+ if (!eventId) {
+ alert("No event selected");
+ window.location.href = ROUTES.eventListing;
+ return;
+ }
+
+ bindFilters();
+
+ try {
+ await loadVolunteers();
+ } catch (err) {
+ els.table.innerHTML = `
+ Failed to load volunteers
+ `;
+ }
+});
diff --git a/README.md b/README.md
deleted file mode 100644
index f57ee73..0000000
--- a/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# aidloop-web
-Web app for AidLoop platform
diff --git a/Images/AIDLoopLogo.jpeg b/assets/Images/AIDLoopLogo.jpeg
similarity index 100%
rename from Images/AIDLoopLogo.jpeg
rename to assets/Images/AIDLoopLogo.jpeg
diff --git a/Images/LandingPageImage.jpeg b/assets/Images/LandingPageImage.jpeg
similarity index 100%
rename from Images/LandingPageImage.jpeg
rename to assets/Images/LandingPageImage.jpeg
diff --git a/assets/Images/Logo.png b/assets/Images/Logo.png
new file mode 100644
index 0000000..24513be
Binary files /dev/null and b/assets/Images/Logo.png differ
diff --git a/Images/Sign_Up.png b/assets/Images/Sign_Up.png
similarity index 100%
rename from Images/Sign_Up.png
rename to assets/Images/Sign_Up.png
diff --git a/Images/certificate.png b/assets/Images/certificate.png
similarity index 100%
rename from Images/certificate.png
rename to assets/Images/certificate.png
diff --git a/Images/check_circle.png b/assets/Images/check_circle.png
similarity index 100%
rename from Images/check_circle.png
rename to assets/Images/check_circle.png
diff --git a/Images/clipboard.png b/assets/Images/clipboard.png
similarity index 100%
rename from Images/clipboard.png
rename to assets/Images/clipboard.png
diff --git a/Images/event.png b/assets/Images/event.png
similarity index 100%
rename from Images/event.png
rename to assets/Images/event.png
diff --git a/Images/instagram.png b/assets/Images/instagram.png
similarity index 100%
rename from Images/instagram.png
rename to assets/Images/instagram.png
diff --git a/Images/linkedin.png b/assets/Images/linkedin.png
similarity index 100%
rename from Images/linkedin.png
rename to assets/Images/linkedin.png
diff --git a/Images/shield_check.png b/assets/Images/shield_check.png
similarity index 100%
rename from Images/shield_check.png
rename to assets/Images/shield_check.png
diff --git a/Images/user.png b/assets/Images/user.png
similarity index 100%
rename from Images/user.png
rename to assets/Images/user.png
diff --git a/assets/Images/volunteer.png b/assets/Images/volunteer.png
new file mode 100644
index 0000000..eefafd0
Binary files /dev/null and b/assets/Images/volunteer.png differ
diff --git a/Images/x.png b/assets/Images/x.png
similarity index 100%
rename from Images/x.png
rename to assets/Images/x.png
diff --git a/assets/css/badges.css b/assets/css/badges.css
new file mode 100644
index 0000000..0c78263
--- /dev/null
+++ b/assets/css/badges.css
@@ -0,0 +1,30 @@
+.status-badge, .badge {
+ display: inline-block;
+ min-width: 6.8rem;
+ text-align: center;
+ padding: 0.58rem 0.95rem;
+ border-radius: 0.42rem;
+ color: #fff;
+ font-size: 0.84rem;
+ font-weight: 600;
+}
+
+.status-issued, .confirmed {
+ background: #2e8b57;
+}
+
+.status-pending {
+ background: #f5ae42;
+}
+
+.status-cancelled {
+ background: #dc2626;
+}
+
+.status-draft {
+ background: #6f809b;
+}
+
+.status-completed {
+ background: #1f80ea;
+}
diff --git a/assets/css/forms.css b/assets/css/forms.css
new file mode 100644
index 0000000..3b352f2
--- /dev/null
+++ b/assets/css/forms.css
@@ -0,0 +1,42 @@
+.form-group {
+ margin-bottom: 1rem;
+}
+
+.form-group label {
+ display: block;
+ font-size: 0.9rem;
+ font-weight: 500;
+ margin-bottom: 0.35rem;
+}
+
+input, textarea, select {
+ width: 100%;
+ padding: 0.85rem 0.95rem;
+ border: 1px solid #d1d5db;
+ border-radius: 0.7rem;
+ background: #fff;
+ outline: none;
+}
+
+textarea {
+ min-height: 6rem;
+ resize: vertical;
+}
+
+.primary-btn {
+ border: none;
+ background: #223f6b;
+ color: #fff;
+ padding: 0.95rem 1.35rem;
+ border-radius: 0.75rem;
+ cursor: pointer;
+}
+
+.secondary-btn {
+ border: 1px solid #223f6b;
+ background: transparent;
+ color: #223f6b;
+ padding: 0.95rem 1.35rem;
+ border-radius: 0.75rem;
+ cursor: pointer;
+}
diff --git a/assets/css/layout.css b/assets/css/layout.css
new file mode 100644
index 0000000..fa7d4ff
--- /dev/null
+++ b/assets/css/layout.css
@@ -0,0 +1,92 @@
+.dashboard-layout {
+ display: flex;
+ min-height: 100vh;
+}
+
+.sidebar {
+ width: 15rem;
+ background: #1f3b63;
+ color: #fff;
+ padding: 1.5rem 1rem;
+ flex-shrink: 0;
+}
+
+.sidebar-logo {
+ text-align: center;
+ margin-bottom: 2rem;
+}
+
+.sidebar-logo img {
+ width: 5.5rem;
+}
+
+.sidebar-menu {
+ list-style: none;
+ display: flex;
+ flex-direction: column;
+ gap: 0.55rem;
+}
+
+.sidebar-menu li a, .sidebar-logout {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ gap: 0.85rem;
+ color: #fff;
+ padding: 0.95rem 1rem;
+ border-radius: 0.65rem;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+}
+
+.sidebar-menu li.active a, .sidebar-menu li a:hover {
+ background: #1f80ea;
+}
+
+.sidebar-logout:hover {
+ background: #dc2626;
+}
+
+.main-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+}
+
+.topbar {
+ background: #fff;
+ padding: 2rem 2.2rem 1.2rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+}
+
+.page-content {
+ padding: 2.2rem;
+}
+
+.page-title {
+ font-size: 2rem;
+ font-weight: 700;
+ color: #223f6b;
+}
+
+.page-subtitle {
+ margin-top: 0.3rem;
+ font-size: 0.9rem;
+ color: #6b7280;
+}
+
+@media (max-width: 64rem) {
+ .dashboard-layout {
+ flex-direction: column;
+}
+ .sidebar {
+ width: 100%;
+}
+ .topbar {
+ flex-direction: column;
+ gap: 1rem;
+}
+}
diff --git a/assets/css/shared.css b/assets/css/shared.css
new file mode 100644
index 0000000..bbe4c62
--- /dev/null
+++ b/assets/css/shared.css
@@ -0,0 +1,28 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+body {
+ background: #f4f7fb;
+ color: #1f2f46;
+}
+a {
+ text-decoration: none;
+}
+button:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+}
+.form-error, .success-message {
+ display: block;
+ font-size: 0.85rem;
+ margin-top: 0.5rem;
+}
+.form-error {
+ color: #dc2626;
+}
+.success-message {
+ color: #16a34a;
+}
diff --git a/assets/css/tables.css b/assets/css/tables.css
new file mode 100644
index 0000000..5acf63d
--- /dev/null
+++ b/assets/css/tables.css
@@ -0,0 +1,34 @@
+.table-wrapper {
+ background: #fff;
+ border-radius: 0.9rem;
+ overflow: hidden;
+ border: 1px solid #d8dfe8;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+thead {
+ background: #eef2f6;
+}
+
+th, td {
+ padding: 1rem;
+ text-align: left;
+ font-size: 0.92rem;
+}
+
+th {
+ color: #35527a;
+ font-weight: 600;
+}
+
+tbody tr {
+ border-bottom: 1px solid #d7dde6;
+}
+
+tbody tr:last-child {
+ border-bottom: none;
+}
diff --git a/assets/js/admin.js b/assets/js/admin.js
new file mode 100644
index 0000000..96f040a
--- /dev/null
+++ b/assets/js/admin.js
@@ -0,0 +1,34 @@
+import { getCurrentUser } from "./auth.js";
+
+export async function hydrateAdminHeader({
+ nameSelector = "#adminName",
+ roleSelector = "#adminRole",
+ avatarSelector = "#adminAvatar"
+} = {}) {
+ const nameEl = document.querySelector(nameSelector);
+ const roleEl = document.querySelector(roleSelector);
+ const avatarEl = document.querySelector(avatarSelector);
+
+ try {
+ const profile = await getCurrentUser();
+
+ if (nameEl) {
+ nameEl.textContent = profile.fullName || profile.name || "Admin";
+ }
+
+ if (roleEl) {
+ roleEl.textContent = profile.role
+ ? profile.role.charAt(0).toUpperCase() + profile.role.slice(1)
+ : "Admin";
+ }
+
+ if (avatarEl && profile.profileImage) {
+ avatarEl.src = profile.profileImage;
+ }
+
+ return profile;
+ } catch (error) {
+ console.error("Failed to load admin header:", error.message);
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/assets/js/api.js b/assets/js/api.js
new file mode 100644
index 0000000..22ba696
--- /dev/null
+++ b/assets/js/api.js
@@ -0,0 +1,34 @@
+import { API_BASE_URL } from "./config.js";
+
+export async function apiRequest(endpoint, options = {}) {
+ const response = await fetch(`${API_BASE_URL}${endpoint}`, {
+ credentials: "include",
+ headers: {
+ "Content-Type": "application/json",
+ ...(options.headers || {})
+ },
+ ...options
+ });
+
+ const contentType = response.headers.get("content-type") || "";
+ const isJson = contentType.includes("application/json");
+ const data = isJson ? await response.json() : await response.blob();
+
+ if (!response.ok) {
+ if (isJson) {
+ throw new Error(data.message || data.error || "Request failed");
+ }
+ throw new Error("Request failed");
+ }
+
+ return data;
+}
+
+export function normalizeArray(payload, keys = []) {
+ if (Array.isArray(payload)) return payload;
+ for (const key of keys) {
+ if (Array.isArray(payload?.[key])) return payload[key];
+ }
+ if (Array.isArray(payload?.data)) return payload.data;
+ return [];
+}
diff --git a/assets/js/auth.js b/assets/js/auth.js
new file mode 100644
index 0000000..f6a36b7
--- /dev/null
+++ b/assets/js/auth.js
@@ -0,0 +1,27 @@
+import { apiRequest } from "./api.js";
+import { ROUTES } from "./config.js";
+
+export async function getCurrentUser() {
+ try {
+ return await apiRequest("/users/me");
+ } catch {
+ return await apiRequest("/user/me");
+ }
+}
+
+export async function requireRole(role, redirectTo) {
+ try {
+ const user = await getCurrentUser();
+ if (String(user.role || "").toLowerCase() !== String(role).toLowerCase()) {
+ window.location.href = redirectTo;
+ return null;
+ }
+ return user;
+ } catch {
+ window.location.href = redirectTo;
+ return null;
+ }
+}
+
+export const requireAdmin = () => requireRole("admin", ROUTES.adminLogin);
+export const requireOrganizer = () => requireRole("organizer", ROUTES.organizerLogin);
diff --git a/assets/js/config.js b/assets/js/config.js
new file mode 100644
index 0000000..4af86c3
--- /dev/null
+++ b/assets/js/config.js
@@ -0,0 +1,28 @@
+export const API_BASE_URL = "https://aidloop-backend.onrender.com/api";
+
+export const ROUTES = {
+ home: "/index.html",
+
+ adminLogin: "/Admin/login/admin-login.html",
+ adminDashboard: "/Admin/dashboard/admin-dashboard.html",
+ adminProfile: "/Admin/profile/admin-profile.html",
+ adminVerificationQueue: "/Admin/verification/verification-queue.html",
+ adminOrganizations: "/Admin/organizations/organization-directory.html",
+ adminEvents: "/Admin/events/events-oversight.html",
+ adminFlags: "/Admin/flags/flags.html",
+ adminUsers: "/Admin/users/user-management.html",
+ adminCertificates: "/Admin/certificates/certificates.html",
+
+ organizerLogin: "/Organizer/login/organizer-login.html",
+ organizerSignup: "/Organizer/signup/organizer-signup.html",
+ organizerVerifyEmail: "/Organizer/verify-email/verify-email.html",
+ organizerEmailVerified: "/Organizer/email-verified/email-verified.html",
+ organizerDashboard: "/Organizer/dashboard/organizer-dashboard.html",
+ organizerCreateEvent: "/Organizer/events/create-event.html",
+ organizerEventListing: "/Organizer/events/event-listing.html",
+ organizerEventDetails: "/Organizer/events/event-details.html",
+ organizerCancelEvent: "/Organizer/events/cancel-event.html",
+ organizerVolunteers: "/Organizer/volunteers/volunteers.html",
+ organizerCertificates: "/Organizer/certificates/organizer-certificates.html",
+ organizerProfile: "/Organizer/profile/organizer-profile.html"
+};
diff --git a/assets/js/logout.js b/assets/js/logout.js
new file mode 100644
index 0000000..03df49e
--- /dev/null
+++ b/assets/js/logout.js
@@ -0,0 +1,16 @@
+import { apiRequest } from "./api.js";
+import { ROUTES } from "./config.js";
+
+export async function logout(redirectTo = ROUTES.home) {
+ try {
+ await apiRequest("/auth/logout", { method: "POST" });
+ } catch {
+ // ignore
+ } finally {
+ localStorage.removeItem("aidloop_admin_email");
+ localStorage.removeItem("aidloop_organizer_email");
+ localStorage.removeItem("aidloop_volunteer_email");
+ sessionStorage.removeItem("aidloop_pending_verification_email");
+ window.location.href = redirectTo;
+ }
+}
diff --git a/assets/js/render.js b/assets/js/render.js
new file mode 100644
index 0000000..191f306
--- /dev/null
+++ b/assets/js/render.js
@@ -0,0 +1,19 @@
+export function renderMessageRow(tbody, colSpan, message) {
+ tbody.innerHTML = `
+
+ ${message}
+
+ `;
+}
+
+export function toggleEmptyState(tableBody, emptyState, hasData) {
+ if (!tableBody || !emptyState) return;
+
+ if (hasData) {
+ tableBody.style.display = "";
+ emptyState.style.display = "none";
+ } else {
+ tableBody.style.display = "";
+ emptyState.style.display = "flex";
+ }
+}
\ No newline at end of file
diff --git a/assets/js/ui.js b/assets/js/ui.js
new file mode 100644
index 0000000..45c96c4
--- /dev/null
+++ b/assets/js/ui.js
@@ -0,0 +1,13 @@
+export function bindFilterButtons(buttons, onChange) {
+ buttons.forEach((button) => {
+ button.addEventListener("click", () => {
+ buttons.forEach((b) => b.classList.remove("active"));
+ button.classList.add("active");
+ onChange(button.dataset.filter);
+ });
+ });
+}
+
+export function renderMessageRow(tbody, colSpan, message) {
+ tbody.innerHTML = `${message} `;
+}
diff --git a/assets/js/utils.js b/assets/js/utils.js
new file mode 100644
index 0000000..3d73540
--- /dev/null
+++ b/assets/js/utils.js
@@ -0,0 +1,30 @@
+export function getQueryParam(name) {
+ return new URLSearchParams(window.location.search).get(name);
+}
+
+export function formatDate(dateValue, month = "short") {
+ if (!dateValue) return "—";
+ const date = new Date(dateValue);
+ if (Number.isNaN(date.getTime())) return dateValue;
+
+ return date.toLocaleDateString("en-GB", {
+ day: "2-digit",
+ month,
+ year: "numeric"
+ });
+}
+
+export function getLocationText(entity) {
+ if (typeof entity?.location === "string" && entity.location.trim()) {
+ return entity.location;
+ }
+
+ if (entity?.location && typeof entity.location === "object") {
+ return [
+ entity.location.venue,
+ entity.location.city || entity.location.state
+ ].filter(Boolean).join(", ");
+ }
+
+ return entity?.city || entity?.state || "—";
+}
diff --git a/index.css b/index.css
new file mode 100644
index 0000000..6501eea
--- /dev/null
+++ b/index.css
@@ -0,0 +1,507 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: "Poppins", sans-serif;
+}
+
+html {
+ scroll-behavior: smooth;
+}
+
+body {
+ background: #f5f7fa;
+ color: #223f6b;
+}
+
+a {
+ text-decoration: none;
+}
+
+.site-header {
+ width: 100%;
+ background: #f5f7fa;
+ position: sticky;
+ top: 0;
+ z-index: 50;
+}
+
+.navbar {
+ max-width: 85rem;
+ margin: 0 auto;
+ padding: 1.4rem 2rem;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1.5rem;
+}
+
+.logo img {
+ width: 11rem;
+ display: block;
+}
+
+.nav-links {
+ list-style: none;
+ display: flex;
+ align-items: center;
+ gap: 2rem;
+}
+
+.nav-links a {
+ color: #223f6b;
+ font-size: 1.2rem;
+ font-weight: 700;
+}
+
+.nav-links a:hover {
+ color: #1f80ea;
+ transform: translateY(-3px);
+}
+
+.nav-dropdown {
+ position: relative;
+}
+
+.nav-link-btn {
+ border: none;
+ background: transparent;
+ color: #223f6b;
+ font-size: 1.05rem;
+ font-weight: 700;
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.45rem;
+}
+
+.nav-link-btn:hover {
+ color: #1f80ea;
+ transform: translateY(-3px);
+}
+
+.nav-link-btn i {
+ font-size: 0.8rem;
+}
+
+.dropdown-menu {
+ position: absolute;
+ top: calc(100% + 0.8rem);
+ left: 0;
+ min-width: 13rem;
+ background: #ffffff;
+ border-radius: 0.85rem;
+ box-shadow: 0 0.8rem 2rem rgba(0, 0, 0, 0.1);
+ padding: 0.6rem;
+ display: none;
+ flex-direction: column;
+ gap: 0.25rem;
+ z-index: 100;
+}
+
+.dropdown-menu.show {
+ display: flex;
+}
+
+.dropdown-menu a {
+ padding: 0.85rem 1rem;
+ border-radius: 0.65rem;
+ color: #223f6b;
+ font-size: 0.98rem;
+ font-weight: 700;
+}
+
+.dropdown-menu a:hover {
+ /* background: #eef5ff; */
+ color: #1f80ea;
+}
+
+.nav-buttons {
+ display: flex;
+ align-items: center;
+}
+
+.primary-btn,
+.secondary-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 1rem;
+ font-weight: 600;
+ transition: 0.2s ease;
+ cursor: pointer;
+}
+
+.primary-btn {
+ background: #223f6b;
+ color: #ffffff;
+ border: none;
+}
+
+.primary-btn:hover {
+ background-color: #1f80ea;
+ transform: translateY(-3px);
+}
+
+.secondary-btn {
+ background: transparent;
+ color: #223f6b;
+ border: 0.12rem solid #1f80ea;
+}
+
+.secondary-btn:hover {
+ background: #1f80ea;
+ transform: translateY(-3px);
+}
+
+.nav-signup-btn {
+ min-width: 10rem;
+ padding: 0.7rem 1.7rem;
+ font-size: 1.05rem;
+}
+
+.hero {
+ max-width: 85rem;
+ margin: 0 auto;
+ padding: 3.5rem 2rem 5rem;
+ display: grid;
+ grid-template-columns: 1.05fr 0.95fr;
+ gap: 3rem;
+ align-items: center;
+}
+
+.hero-text .title {
+ font-size: 4rem;
+ line-height: 1;
+ font-weight: 700;
+ color: #1f3a5f;
+}
+
+.hero-text .title-large {
+ font-size: 4rem;
+ line-height: 1;
+ font-weight: 700;
+ margin: 0.4rem 0;
+}
+
+.title .title-large {
+ margin-top: -80px;
+}
+
+.orange {
+ color: #ffb347;
+}
+
+.green {
+ color: #2e8b57;
+}
+
+.description {
+ max-width: 37rem;
+ font-size: 1.2rem;
+ line-height: 1.7;
+ color: #6c7c95;
+ margin-top: 1.2rem;
+}
+
+.hero-buttons {
+ display: flex;
+ gap: 1.4rem;
+ margin-top: 1rem;
+}
+
+.hero-buttons .primary-btn,
+.hero-buttons .secondary-btn {
+ min-width: 10rem;
+ padding: 0.5rem 0.5rem;
+ font-size: 1.2rem;
+}
+
+.hero-image img {
+ margin-top: -30px;
+ width: 600px;
+ object-fit: cover;
+ display: block;
+ border-top-left-radius: 50px;
+ border-bottom-right-radius: 50px;
+ box-shadow:0 10px 30px rgba(0,0,0,0.1);
+
+}
+
+.section-title {
+ text-align: center;
+ margin-bottom: 2.5rem;
+}
+
+.section-title h2 {
+ font-size: 3.5rem;
+ font-weight: 800;
+ color: #222;
+}
+
+.how-it-works {
+ max-width: 85rem;
+ margin: 0 auto;
+ padding: 2rem 2rem 5rem;
+}
+
+.how-it-works-card-container {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 2.3rem;
+}
+
+.how-it-works-card {
+ background: #ffffff;
+ border: 0.18rem solid #223f6b;
+ border-radius: 1.3rem;
+ padding: 2rem 1.6rem;
+ text-align: center;
+ min-height: 24rem;
+}
+
+.card-icon1 img {
+ width: 5rem;
+ margin-bottom: 1rem;
+}
+
+.how-it-works-card h4 {
+ font-size: 1.9rem;
+ color: #223f6b;
+ margin-bottom: 1rem;
+}
+
+.how-it-works-card p {
+ font-size: 1.35rem;
+ line-height: 1.7;
+ color: #6c7c95;
+}
+
+.benefits {
+ max-width: 85rem;
+ margin: 0 auto;
+ padding: 1rem 2rem 5rem;
+}
+
+.benefits-title {
+ text-align: left;
+}
+
+.benefits-card-container {
+ display: flex;
+ flex-direction: column;
+ gap: 1.8rem;
+}
+
+.benefits-card {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 2rem;
+}
+
+.benefit-text {
+ flex: 1;
+}
+
+.benefits-card h5 {
+ font-size: 2.6rem;
+ color: #223f6b;
+ margin-bottom: 0.8rem;
+}
+
+.benefits-card p {
+ max-width: 50rem;
+ font-size: 1.55rem;
+ line-height: 1.6;
+ color: #6c7c95;
+}
+
+.card-icon img {
+ width: 5.4rem;
+ flex-shrink: 0;
+}
+
+.line {
+ margin-top: 1.8rem;
+ border: none;
+ height: 0.12rem;
+ background: #1f80ea;
+}
+
+.start-event {
+ max-width: 85rem;
+ margin: 0 auto;
+ padding: 2rem 2rem 5rem;
+}
+
+.start-event-content {
+ text-align: center;
+}
+
+.start-event-title h3 {
+ font-size: 3.4rem;
+ color: #111;
+ margin-bottom: 2rem;
+ font-weight: 800;
+}
+
+.start-event-content p {
+ max-width: 58rem;
+ margin: 0 auto 2rem;
+ font-size: 1.9rem;
+ line-height: 1.6;
+ color: #222;
+}
+
+.cta-btn {
+ min-width: 10rem;
+ padding: 0.7rem 1.7rem;
+ font-size: 1.2rem;
+}
+
+.footer {
+ background: #223f6b;
+ color: #ffffff;
+ margin-top: 2rem;
+}
+
+.footer-container {
+ max-width: 85rem;
+ margin: 0 auto;
+ padding: 3rem 2rem;
+ display: grid;
+ grid-template-columns: 1.5fr 1fr 1fr 1fr;
+ gap: 2rem;
+}
+
+.footer-logo-text {
+ font-size: 2.2rem;
+ margin-bottom: 0.7rem;
+}
+
+.tagline,
+.help-text,
+.copyright {
+ color: #dbe4ef;
+}
+
+.footer-col h3 {
+ font-size: 1.5rem;
+ margin-bottom: 1rem;
+}
+
+.footer-col ul {
+ list-style: none;
+ display: flex;
+ flex-direction: column;
+ gap: 0.7rem;
+}
+
+.footer-col a {
+ color: #dbe4ef;
+ font-size: 1.2rem;
+}
+
+.footer-col a:hover {
+ color: #ffffff;
+}
+
+.social-icons {
+ display: flex;
+ gap: 0.8rem;
+ margin-bottom: 1rem;
+}
+
+.social-icons img {
+ width: 2rem;
+ height: 2rem;
+ object-fit: contain;
+}
+
+.copyright {
+ margin-top: 3rem;
+ font-size: 0.95rem;
+}
+
+@media (max-width: 68rem) {
+ .hero {
+ grid-template-columns: 1fr;
+ }
+
+ .how-it-works-card-container {
+ grid-template-columns: 1fr;
+ }
+
+ .footer-container {
+ grid-template-columns: 1fr 1fr;
+ }
+
+ .hero-text .title,
+ .hero-text .title-large {
+ font-size: 3rem;
+ }
+
+ .description,
+ .benefits-card p,
+ .start-event-content p {
+ font-size: 1.2rem;
+ }
+
+ .benefits-card h5 {
+ font-size: 2rem;
+ }
+}
+
+@media (max-width: 48rem) {
+ .navbar {
+ flex-wrap: wrap;
+ justify-content: center;
+ }
+
+ .nav-links {
+ flex-wrap: wrap;
+ justify-content: center;
+ }
+
+ .hero,
+ .how-it-works,
+ .benefits,
+ .start-event {
+ padding-left: 1.2rem;
+ padding-right: 1.2rem;
+ }
+
+ .footer-container {
+ grid-template-columns: 1fr;
+ }
+
+ .hero-buttons {
+ flex-direction: column;
+ }
+
+ .hero-buttons .primary-btn,
+ .hero-buttons .secondary-btn,
+ .cta-btn {
+ width: 100%;
+ }
+
+ .section-title h2 {
+ font-size: 2.5rem;
+ }
+
+ .start-event-title h3 {
+ font-size: 2.3rem;
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/index.html b/index.html
index bd25046..6475174 100644
--- a/index.html
+++ b/index.html
@@ -1,44 +1,317 @@
-
-
- Aid Loop Organizational login
-
+
+
+ AIDLoop - Recruit Verified Volunteers
+
+
+
+
+
+
-