Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width,initial-scale=1" /> | |
| <title>Profile - CTRL + ALT + HEAL</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="style.css" /> | |
| <script src="script.js"></script> | |
| </head> | |
| <body class="bg-[#F7F8F9] min-h-screen"> | |
| <!-- NAVBAR --> | |
| <nav | |
| class="fixed top-0 left-0 w-full z-50 backdrop-blur-md bg-white/20 border-b border-white/30 shadow-md" | |
| > | |
| <div class="flex justify-between items-center w-full px-6 py-4"> | |
| <a | |
| href="index.html" | |
| class="text-2xl font-bold text-black hover:text-[var(--tropical-indigo)] transition" | |
| > | |
| CTRL + ALT + HEAL | |
| </a> | |
| <ul class="hidden md:flex space-x-6 font-medium text-gray-800"> | |
| <li> | |
| <a href="index.html" class="nav-link" style="display: none">Home</a> | |
| </li> | |
| <li><a href="profile.html" class="nav-link">Profile</a></li> | |
| <li><a href="analyzer.html" class="nav-link">Analyzer</a></li> | |
| <li><a href="past_data.html" class="nav-link">Past Reports</a></li> | |
| <li id="authNavItem"> | |
| <a href="login.html" class="nav-link">Login</a> | |
| </li> | |
| </ul> | |
| <button | |
| id="hamburger" | |
| class="md:hidden text-[var(--latte-cream)] text-2xl" | |
| > | |
| ☰ | |
| </button> | |
| </div> | |
| <ul | |
| id="mobile-menu" | |
| class="hidden flex-col space-y-4 bg-white/30 backdrop-blur-lg border border-white/20 rounded-xl shadow-lg mt-2 p-4 mx-6 md:hidden" | |
| > | |
| <li> | |
| <a | |
| href="index.html" | |
| class="block text-gray-800 hover:text-[var(--tropical-indigo)]" | |
| >Home</a | |
| > | |
| </li> | |
| <li> | |
| <a | |
| href="analyzer.html" | |
| class="block text-gray-800 hover:text-[var(--tropical-indigo)]" | |
| >Analyzer</a | |
| > | |
| </li> | |
| <li> | |
| <a | |
| href="profile.html" | |
| class="block text-gray-800 hover:text-[var(--tropical-indigo)]" | |
| >Profile</a | |
| > | |
| </li> | |
| <li> | |
| <a | |
| href="login.html" | |
| class="block text-gray-800 hover:text-[var(--tropical-indigo)]" | |
| >Login</a | |
| > | |
| </li> | |
| <li> | |
| <a | |
| href="about.html" | |
| class="block text-gray-800 hover:text-[var(--tropical-indigo)]" | |
| >About</a | |
| > | |
| </li> | |
| </ul> | |
| </nav> | |
| <script> | |
| const hamburger = document.getElementById("hamburger"); | |
| const mobileMenu = document.getElementById("mobile-menu"); | |
| hamburger.addEventListener("click", () => | |
| mobileMenu.classList.toggle("hidden") | |
| ); | |
| const currentPath = window.location.pathname.split("/").pop(); | |
| document.querySelectorAll(".nav-link").forEach((link) => { | |
| if (link.getAttribute("href") === currentPath) | |
| link.classList.add("active-page"); | |
| }); | |
| document.querySelectorAll("#mobile-menu a").forEach((link) => { | |
| if (link.getAttribute("href") === currentPath) | |
| link.classList.add("active-page"); | |
| }); | |
| </script> | |
| <div class="container mx-auto px-6 pt-24"> | |
| <div class="bg-white border border-gray-200 rounded-lg p-6 shadow mb-6"> | |
| <!-- PROFILE VIEW --> | |
| <div id="profileViewSection"> | |
| <div class="flex items-start space-x-6 mb-10"> | |
| <div class="flex flex-col items-center"> | |
| <img src="https://cdn.mos.cms.futurecdn.net/ARWms77gyVZ5u2MZkQqcKc-1256-80.jpg.webp" | |
| alt="Profile Tree" | |
| class="w-32 h-32 object-cover rounded-full border border-gray-300"> | |
| <button id="editProfileBtn" class="mt-2 px-4 py-2 rounded"> | |
| Edit Profile | |
| </button> | |
| <a href="analyzer.html"> | |
| <button id="analyzerBtn" class="mt-2 px-4 py-2 rounded"> | |
| Go To Analyzer | |
| </button> | |
| </a> | |
| </div> | |
| <div> | |
| <h2 class="text-xl font-semibold mb-4">Your Profile</h2> | |
| <p class="mb-2"> | |
| Name: <span id="userName" class="font-medium">Loading...</span> | |
| </p> | |
| <p class="mb-2"> | |
| DOB: <span id="userDOB" class="font-medium">Loading...</span> | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- PROFILE EDIT --> | |
| <div id="profileEditSection" class="hidden mb-10"> | |
| <div class="flex flex-col sm:flex-row items-start space-x-6"> | |
| <div class="flex flex-col items-center mb-4 sm:mb-0"> | |
| <img src="https://cdn.mos.cms.futurecdn.net/ARWms77gyVZ5u2MZkQqcKc-1256-80.jpg.webp" | |
| alt="Profile Tree" | |
| class="w-32 h-32 object-cover rounded-full border border-gray-300"> | |
| </div> | |
| <div class="flex-1"> | |
| <h1 class="text-3xl font-semibold mb-4">Edit Profile</h1> | |
| <div class="space-y-4"> | |
| <div> | |
| <label for="inputName" class="block text-sm font-medium" | |
| >Name</label | |
| > | |
| <input | |
| type="text" | |
| id="inputName" | |
| class="w-full border rounded px-3 py-2 focus:ring-2 focus:ring-[#6B9080] focus:outline-none" | |
| /> | |
| </div> | |
| <div> | |
| <label for="inputDOB" class="block text-sm font-medium" | |
| >Date of Birth</label | |
| > | |
| <input | |
| type="date" | |
| id="inputDOB" | |
| class="w-full border rounded px-3 py-2 focus:ring-2 focus:ring-[#6B9080] focus:outline-none" | |
| /> | |
| </div> | |
| </div> | |
| <div class="flex space-x-2 mt-4"> | |
| <button id="saveProfileBtn" class="px-4 py-2 rounded"> | |
| Save | |
| </button> | |
| <button id="cancelProfileBtn" class="px-4 py-2 rounded"> | |
| Cancel | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- RANGE BARS --> | |
| <h2 class="text-xl font-semibold mb-4">Your Current Health Summary</h2> | |
| <div class="tabs"> | |
| <div class="tab active" data-tab="out-range"> | |
| Out of Range <span class="count-badge">0</span> | |
| </div> | |
| <div class="tab" data-tab="in-range"> | |
| In Range <span class="count-badge">0</span> | |
| </div> | |
| </div> | |
| <div class="tab-content active" id="out-range"></div> | |
| <div class="tab-content" id="in-range"></div> | |
| </div> | |
| </div> | |
| <script type="module"> | |
| import { initializeApp } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-app.js"; | |
| import { | |
| getAuth, | |
| onAuthStateChanged, | |
| signOut, | |
| } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-auth.js"; | |
| import { | |
| getFirestore, | |
| doc, | |
| getDoc, | |
| updateDoc, | |
| } from "https://www.gstatic.com/firebasejs/9.22.0/firebase-firestore.js"; | |
| const firebaseConfig = { | |
| apiKey: "AIzaSyAPhM_Ee7cLzyKHs5zyFy8g5ZOk9-pubRI", | |
| authDomain: "login-tutorial-7a9e1.firebaseapp.com", | |
| projectId: "login-tutorial-7a9e1", | |
| storageBucket: "login-tutorial-7a9e1.firebasestorage.app", | |
| messagingSenderId: "491093197824", | |
| appId: "1:491093197824:web:9f86659034af7e6a8244e5", | |
| measurementId: "G-JM7T9N6ZLZ" | |
| }; | |
| const app = initializeApp(firebaseConfig); | |
| const auth = getAuth(app); | |
| const db = getFirestore(app); | |
| let currentUser = null; | |
| const homeNavDesktop = document.querySelector( | |
| 'ul.md\\:flex li a[href="index.html"]' | |
| )?.parentElement; | |
| const homeNavMobile = document.querySelector( | |
| '#mobile-menu a[href="index.html"]' | |
| )?.parentElement; | |
| // Fetch measurements from backend API | |
| async function getUserMeasurements(userId) { | |
| try { | |
| const url = api("user_measurements/", { user_id: userId }); | |
| const res = await fetch(url); | |
| if (!res.ok) throw new Error("Failed to fetch measurements"); | |
| const data = await res.json(); | |
| return (data.measurements || []).map((m) => ({ | |
| name: m.measurement_type, | |
| value: Number(m.value), | |
| min: Number(m.min), | |
| max: Number(m.max), | |
| tab: m.status === "HIGH" || m.status === "LOW" ? "out-range" : "in-range", | |
| impacts: m.impacts || [], | |
| })); | |
| } catch (e) { | |
| console.error("Error fetching user measurements:", e); | |
| return []; | |
| } | |
| } | |
| function getLatestByType(measurements) { | |
| const latest = {}; | |
| measurements.forEach(m => { | |
| if (!latest[m.name] || new Date(m.timestamp) > new Date(latest[m.name].timestamp)) { | |
| latest[m.name] = m; | |
| } | |
| }); | |
| return Object.values(latest); | |
| } | |
| // Render bars in the UI based on measurement array | |
| function renderBars(measurements) { | |
| console.log("measurements",measurements); | |
| const counts = { "in-range": 0, "out-range": 0 }; | |
| const tabs = document.querySelectorAll(".tab"); | |
| const contents = document.querySelectorAll(".tab-content"); | |
| // Clear existing content | |
| contents.forEach((c) => (c.innerHTML = "")); | |
| measurements.forEach((m) => { | |
| counts[m.tab]++; | |
| const container = document.getElementById(m.tab); | |
| const card = document.createElement("div"); | |
| card.className = "range-card"; | |
| const title = document.createElement("h3"); | |
| title.className = "measurement-title"; | |
| title.textContent = m.name; | |
| card.appendChild(title); | |
| if (m.impacts?.length) { | |
| const impactLabel = document.createElement("div"); | |
| impactLabel.className = "impact-label"; | |
| impactLabel.textContent = "Impacts: " + m.impacts.join(", "); | |
| card.appendChild(impactLabel); | |
| } | |
| const barContainer = document.createElement("div"); | |
| barContainer.className = "range-bar-container"; | |
| const bar = document.createElement("div"); | |
| bar.className = "range-bar"; | |
| let normalPercent = 100; | |
| let overflowPercent = 0; | |
| if (m.tab === "out-range" && m.value > m.max) { | |
| normalPercent = ((m.max - m.min) / (m.value - m.min)) * 100; | |
| overflowPercent = 100 - normalPercent; | |
| } | |
| const normalDiv = document.createElement("div"); | |
| normalDiv.className = "normal-range"; | |
| normalDiv.style.width = normalPercent + "%"; | |
| bar.appendChild(normalDiv); | |
| if (overflowPercent > 0) { | |
| const overflowDiv = document.createElement("div"); | |
| overflowDiv.className = "overflow-range"; | |
| overflowDiv.style.left = normalPercent + "%"; | |
| overflowDiv.style.width = overflowPercent + "%"; | |
| bar.appendChild(overflowDiv); | |
| } | |
| let valuePercent = ((m.value - m.min) / (m.max - m.min)) * 100; | |
| valuePercent = Math.min(Math.max(valuePercent, 0), 100); | |
| const marker = document.createElement("div"); | |
| marker.className = "marker"; | |
| marker.style.left = valuePercent + "%"; | |
| bar.appendChild(marker); | |
| const valueLabel = document.createElement("div"); | |
| valueLabel.className = "value-label"; | |
| valueLabel.style.left = valuePercent + "%"; | |
| valueLabel.textContent = m.value; | |
| bar.appendChild(valueLabel); | |
| barContainer.appendChild(bar); | |
| const minMaxDiv = document.createElement("div"); | |
| minMaxDiv.className = "min-max-labels relative w-full"; | |
| const minLabel = document.createElement("span"); | |
| minLabel.textContent = "Min: " + m.min; | |
| minLabel.style.position = "absolute"; | |
| minLabel.style.left = "0"; | |
| const maxLabel = document.createElement("span"); | |
| maxLabel.textContent = "Max: " + m.max; | |
| maxLabel.style.position = "absolute"; | |
| let maxPercent = 100; | |
| if (m.value > m.max) maxPercent = ((m.max - m.min) / (m.value - m.min)) * 100; | |
| maxLabel.style.left = maxPercent + "%"; | |
| maxLabel.style.transform = "translateX(-100%)"; | |
| minMaxDiv.appendChild(minLabel); | |
| minMaxDiv.appendChild(maxLabel); | |
| barContainer.appendChild(minMaxDiv); | |
| card.appendChild(barContainer); | |
| container.appendChild(card); | |
| }); | |
| // Update tab badges | |
| tabs.forEach((tab) => { | |
| const t = tab.dataset.tab; | |
| const badge = tab.querySelector(".count-badge"); | |
| badge.textContent = counts[t]; | |
| tab.addEventListener("click", () => { | |
| tabs.forEach((t) => t.classList.remove("active")); | |
| tab.classList.add("active"); | |
| contents.forEach((c) => | |
| c.id === t ? c.classList.add("active") : c.classList.remove("active") | |
| ); | |
| }); | |
| }); | |
| } | |
| // ✅ Auth State Handling | |
| onAuthStateChanged(auth, async (user) => { | |
| const authNavItem = document.getElementById("authNavItem"); | |
| if (user) { | |
| currentUser = user; | |
| await loadUserProfile(); | |
| // ✅ fetch and render bars after user is ready | |
| console.log("currentuser data",currentUser); | |
| // const measurements = await getUserMeasurements(currentUser.uid); | |
| let measurements = await getUserMeasurements(currentUser.email); | |
| measurements = getLatestByType(measurements); | |
| renderBars(measurements); | |
| authNavItem.innerHTML = '<button onclick="logout()" style="color:red">Logout</button>'; | |
| if (homeNavDesktop) homeNavDesktop.style.display = "none"; | |
| if (homeNavMobile) homeNavMobile.style.display = "none"; | |
| } else { | |
| authNavItem.innerHTML = '<a href="login.html">Login</a>'; | |
| if (homeNavDesktop) homeNavDesktop.style.display = ""; | |
| if (homeNavMobile) homeNavMobile.style.display = ""; | |
| window.location.href = "login.html"; | |
| } | |
| }); | |
| async function loadUserProfile() { | |
| try { | |
| const userDoc = await getDoc(doc(db, "users", currentUser.uid)); | |
| if (userDoc.exists()) { | |
| const userData = userDoc.data(); | |
| document.getElementById("userName").textContent = | |
| userData.name || "Name not set"; | |
| document.getElementById("userDOB").textContent = | |
| userData.dob || "DOB not set"; | |
| localStorage.setItem("userName", userData.name || ""); | |
| localStorage.setItem("userDOB", userData.dob || ""); | |
| } | |
| } catch (e) { | |
| console.error("Error loading user profile:", e); | |
| } | |
| } | |
| window.saveProfile = async (name, dob) => { | |
| try { | |
| await updateDoc(doc(db, "users", currentUser.uid), { name, dob }); | |
| localStorage.setItem("userName", name); | |
| localStorage.setItem("userDOB", dob); | |
| return true; | |
| } catch (e) { | |
| console.error("Error updating profile:", e); | |
| return false; | |
| } | |
| }; | |
| window.logout = async () => { | |
| try { | |
| await signOut(auth); | |
| localStorage.clear(); | |
| window.location.href = "login.html"; | |
| } catch (e) { | |
| console.error("Error signing out:", e); | |
| } | |
| }; | |
| document.addEventListener("DOMContentLoaded", async () => { | |
| const profileView = document.getElementById("profileViewSection"); | |
| const profileEdit = document.getElementById("profileEditSection"); | |
| const editBtn = document.getElementById("editProfileBtn"); | |
| const saveBtn = document.getElementById("saveProfileBtn"); | |
| const cancelBtn = document.getElementById("cancelProfileBtn"); | |
| const inputName = document.getElementById("inputName"); | |
| const inputDOB = document.getElementById("inputDOB"); | |
| editBtn.addEventListener("click", () => { | |
| inputName.value = document.getElementById("userName").textContent; | |
| inputDOB.value = document.getElementById("userDOB").textContent; | |
| profileView.classList.add("hidden"); | |
| profileEdit.classList.remove("hidden"); | |
| }); | |
| saveBtn.addEventListener("click", async () => { | |
| const nameVal = inputName.value.trim(); | |
| const dobVal = inputDOB.value; | |
| const success = await window.saveProfile(nameVal, dobVal); | |
| if (success) { | |
| document.getElementById("userName").textContent = | |
| nameVal || "Name not set"; | |
| document.getElementById("userDOB").textContent = | |
| dobVal || "DOB not set"; | |
| profileEdit.classList.add("hidden"); | |
| profileView.classList.remove("hidden"); | |
| } else { | |
| alert("Error saving profile. Please try again."); | |
| } | |
| }); | |
| cancelBtn.addEventListener("click", () => { | |
| profileEdit.classList.add("hidden"); | |
| profileView.classList.remove("hidden"); | |
| }); | |
| // RANGE BARS LOGIC | |
| const tabs = document.querySelectorAll(".tab"); | |
| const contents = document.querySelectorAll(".tab-content"); | |
| }); | |
| </script> | |
| </body> | |
| </html> |