weight-tracker / index.html
J0BL1N's picture
when user clicks logout button page needs to full refresh - Follow Up Deployment
5655d39 verified
Raw
History Blame Contribute Delete
25.7 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Weight Tracker</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://unpkg.com/feather-icons"></script>
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<style>
.tooltip {
position: absolute;
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 5px 10px;
border-radius: 5px;
font-size: 14px;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s;
z-index: 1000;
}
.chart-container:hover .tooltip {
opacity: 1;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="text-center mb-12 relative" data-aos="fade-down">
<div class="absolute top-0 right-0 flex items-center space-x-3">
<div class="flex items-center space-x-3">
<div id="userInfo" style="display: none;" class="flex flex-row items-center gap-2 flex-nowrap">
<div class="bg-indigo-100 text-indigo-700 px-4 py-2 rounded-full font-medium inline-flex items-center">
<i data-feather="user" class="mr-2"></i>
<span id="userName">User</span>
</div>
<button onclick="signOut()" class="bg-gray-200 text-gray-700 px-4 py-2 rounded-full font-medium hover:bg-gray-300 transition duration-200 inline-flex items-center">
<i data-feather="log-out" class="mr-2"></i>Logout
</button>
</div>
<button onclick="showLogin()" id="loginButton" class="bg-indigo-600 text-white font-medium hover:bg-indigo-700 transition duration-200 inline-flex items-center gap-2 px-4 py-2 rounded-full whitespace-nowrap">
<i data-feather="log-in" class="w-4 h-4"></i>Login
</button>
</div>
</div>
<h1 class="text-4xl font-bold text-indigo-700 mb-2">Weight Tracker</h1>
<p class="text-gray-600">Visualizing your health journey</p>
</header>
<!-- Login Modal -->
<div id="loginModal" style="display: none;" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
<div class="mt-3 text-center">
<h3 class="text-lg font-medium text-gray-900">Login</h3>
<div class="mt-2 px-7 py-3">
<input type="email" id="loginEmail" placeholder="Email" class="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 focus:outline-none focus:ring-2 focus:ring-indigo-500">
<input type="password" id="loginPassword" placeholder="Password" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
<div class="text-sm text-center mb-4">
<a href="#" onclick="showSignup()" class="text-indigo-600 hover:text-indigo-800">Create an account</a>
</div>
<div class="flex items-center justify-between px-4 py-3">
<button onclick="closeLogin()" class="px-4 py-2 bg-gray-300 text-gray-700 rounded-md hover:bg-gray-400">
Cancel
</button>
<button onclick="handleLogin()" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">
Login
</button>
</div>
</div>
</div>
</div>
<!-- Signup Modal -->
<div id="signupModal" style="display: none;" class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
<div class="mt-3 text-center">
<h3 class="text-lg font-medium text-gray-900">Create Account</h3>
<div class="mt-2 px-7 py-3">
<input type="text" id="signupName" placeholder="Username" class="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 focus:outline-none focus:ring-2 focus:ring-indigo-500">
<input type="email" id="signupEmail" placeholder="Email" class="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 focus:outline-none focus:ring-2 focus:ring-indigo-500">
<input type="password" id="signupPassword" placeholder="Password" class="w-full px-3 py-2 border border-gray-300 rounded-md mb-3 focus:outline-none focus:ring-2 focus:ring-indigo-500">
<input type="password" id="signupConfirmPassword" placeholder="Confirm Password" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
<div class="text-sm text-center mb-4">
<a href="#" onclick="showLogin()" class="text-indigo-600 hover:text-indigo-800">Already have an account? Login</a>
</div>
<div class="flex items-center justify-between px-4 py-3">
<button onclick="closeSignup()" class="px-4 py-2 bg-gray-300 text-gray-700 rounded-md hover:bg-gray-400">
Cancel
</button>
<button onclick="handleSignup()" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">
Sign Up
</button>
</div>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-lg p-6 mb-8" data-aos="fade-up">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-semibold text-gray-800">Progress Chart</h2>
<div class="flex space-x-2">
<button id="weekBtn" class="px-4 py-2 bg-indigo-100 text-indigo-700 rounded-lg font-medium">Week</button>
<button id="monthBtn" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg font-medium">Month</button>
<button id="yearBtn" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg font-medium">Year</button>
</div>
</div>
<div class="chart-container relative h-96">
<canvas id="weightChart"></canvas>
<div class="tooltip" id="chartTooltip"></div>
</div>
</div>
<div class="grid md:grid-cols-2 gap-6">
<div class="bg-white rounded-xl shadow-lg p-6" data-aos="fade-right">
<h2 class="text-xl font-semibold text-gray-800 mb-4">Add New Entry</h2>
<form id="weightForm" class="space-y-4">
<div>
<label for="date" class="block text-sm font-medium text-gray-700 mb-1">Date (DD/MM/YY)</label>
<input type="text" id="date" placeholder="DD/MM/YY" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
</div>
<div>
<label for="weight" class="block text-sm font-medium text-gray-700 mb-1">Weight (kg)</label>
<input type="number" step="0.1" id="weight" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
</div>
<button type="submit" class="w-full bg-indigo-600 text-white py-2 px-4 rounded-lg hover:bg-indigo-700 transition duration-200 flex items-center justify-center">
<i data-feather="plus" class="mr-2"></i> Add Entry
</button>
</form>
</div>
<div class="bg-white rounded-xl shadow-lg p-6" data-aos="fade-left">
<h2 class="text-xl font-semibold text-gray-800 mb-4">Recent Entries</h2>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Weight</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Change</th>
</tr>
</thead>
<tbody id="entriesTable" class="bg-white divide-y divide-gray-200">
<!-- Entries will be added here dynamically -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
// Initialize AOS and Feather Icons
AOS.init({
duration: 800,
easing: 'ease-in-out'
});
feather.replace();
// Login functionality
function handleLogin() {
const email = document.getElementById('loginEmail').value;
const password = document.getElementById('loginPassword').value;
if (!email || !password) {
alert('Please enter both email and password');
return;
}
// Simple validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
alert('Please enter a valid email address');
return;
}
// Check if user exists
const users = JSON.parse(localStorage.getItem('users') || '[]');
const user = users.find(u => u.email === email && u.password === password);
if (!user) {
alert('Invalid email or password');
return;
}
// Store user info in localStorage
localStorage.setItem('user', JSON.stringify({
name: user.name,
email: user.email
}));
// Update UI
document.getElementById('userName').textContent = user.name;
document.getElementById('loginModal').style.display = 'none';
document.getElementById('userInfo').style.display = 'block';
document.getElementById('loginButton').style.display = 'none';
// Refresh the page to load user data
window.location.reload();
}
// Signup functionality
function handleSignup() {
const name = document.getElementById('signupName').value;
const email = document.getElementById('signupEmail').value;
const password = document.getElementById('signupPassword').value;
const confirmPassword = document.getElementById('signupConfirmPassword').value;
if (!name || !email || !password || !confirmPassword) {
alert('Please fill in all fields');
return;
}
if (password !== confirmPassword) {
alert('Passwords do not match');
return;
}
// Email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
alert('Please enter a valid email address');
return;
}
// Check if user already exists
const users = JSON.parse(localStorage.getItem('users') || '[]');
if (users.some(u => u.email === email)) {
alert('Email already registered. Please login instead.');
return;
}
// Add new user with pending confirmation
users.push({
name: name,
email: email,
password: password,
confirmed: false
});
localStorage.setItem('users', JSON.stringify(users));
// Show confirmation message
document.getElementById('signupModal').style.display = 'none';
alert('Account created! A confirmation email has been sent to ' + email + '. Please check your email to confirm your account.');
}
// Initialize the app
window.onload = function () {
// Check if user is already logged in
const savedUser = localStorage.getItem('user');
if (savedUser) {
try {
const user = JSON.parse(savedUser);
document.getElementById('userName').textContent = user.name;
document.getElementById('userInfo').style.display = 'block';
document.getElementById('loginButton').style.display = 'none';
// Load user-specific data
weightData = getUserWeightData();
calculateChanges();
renderEntriesTable();
renderChart();
} catch (error) {
console.error("Error loading saved user:", error);
localStorage.removeItem('user');
}
} else {
// If no user is logged in, clear any previous data
weightData = [];
calculateChanges();
renderEntriesTable();
renderChart();
}
};
// Load data from localStorage for current user or use empty array
function getUserWeightData() {
const savedUser = localStorage.getItem('user');
if (!savedUser) return [];
try {
const user = JSON.parse(savedUser);
const allUserData = JSON.parse(localStorage.getItem('userWeightData') || '{}');
return allUserData[user.email] || [];
} catch (error) {
console.error("Error loading user data:", error);
return [];
}
}
let weightData = getUserWeightData();
// Sort data by date
weightData.sort((a, b) => {
const [dayA, monthA, yearA] = a.date.split('/').map(Number);
const [dayB, monthB, yearB] = b.date.split('/').map(Number);
const dateA = new Date(2000 + yearA, monthA - 1, dayA);
const dateB = new Date(2000 + yearB, monthB - 1, dayB);
return dateA - dateB;
});
// Save data to localStorage for current user
function saveData() {
const savedUser = localStorage.getItem('user');
if (!savedUser) return;
try {
const user = JSON.parse(savedUser);
const allUserData = JSON.parse(localStorage.getItem('userWeightData') || '{}');
allUserData[user.email] = weightData;
localStorage.setItem('userWeightData', JSON.stringify(allUserData));
} catch (error) {
console.error("Error saving user data:", error);
}
}
// Format dates for display
function formatDate(dateStr) {
// Already in DD/MM/YY format
return dateStr;
}
// Format dates for chart display
function formatChartDate(dateStr) {
const parts = dateStr.split('/');
const day = parts[0];
const month = parts[1];
return `${day}/${month}`;
}
// Calculate changes between entries
function calculateChanges() {
for (let i = 1; i < weightData.length; i++) {
const change = weightData[i].weight - weightData[i-1].weight;
weightData[i].change = change.toFixed(1);
}
if (weightData.length > 0) {
weightData[0].change = '—';
}
}
// Render the table with entries
function renderEntriesTable() {
const tableBody = document.getElementById('entriesTable');
tableBody.innerHTML = '';
weightData.forEach(entry => {
const row = document.createElement('tr');
const changeClass = entry.change === '—' ? '' :
parseFloat(entry.change) < 0 ? 'text-green-500' : 'text-red-500';
const changeSymbol = entry.change === '—' ? '' :
parseFloat(entry.change) < 0 ? '↓' : '↑';
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${formatDate(entry.date)}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${entry.weight} kg</td>
<td class="px-6 py-4 whitespace-nowrap text-sm ${changeClass}">
${changeSymbol} ${entry.change === '—' ? '' : Math.abs(entry.change)} kg
</td>
`;
tableBody.appendChild(row);
});
}
// Initialize and render the chart
let weightChart = null;
function renderChart() {
const ctx = document.getElementById('weightChart').getContext('2d');
// Destroy existing chart if it exists
if (weightChart) {
weightChart.destroy();
}
// Prepare chart data
const labels = weightData.map(entry => formatChartDate(entry.date));
const data = weightData.map(entry => entry.weight);
// Calculate goal line (target is 85kg)
const goalLine = Array(weightData.length).fill(85);
weightChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: 'Your Weight',
data: data,
borderColor: 'rgb(79, 70, 229)',
backgroundColor: 'rgba(79, 70, 229, 0.1)',
borderWidth: 3,
tension: 0.3,
fill: true,
pointBackgroundColor: 'rgb(79, 70, 229)',
pointRadius: 5,
pointHoverRadius: 7
},
{
label: 'Goal (85kg)',
data: goalLine,
borderColor: 'rgb(239, 68, 68)',
borderWidth: 2,
borderDash: [5, 5],
pointRadius: 0,
fill: false
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: false,
title: {
display: true,
text: 'Weight (kg)'
}
},
x: {
title: {
display: true,
text: 'Date'
}
}
},
plugins: {
legend: {
position: 'top',
},
tooltip: {
enabled: false,
external: function(context) {
const tooltip = document.getElementById('chartTooltip');
if (context.tooltip.opacity === 0) {
tooltip.style.opacity = 0;
return;
}
const dataIndex = context.tooltip.dataPoints[0].dataIndex;
const weight = weightData[dataIndex].weight;
const date = formatDate(weightData[dataIndex].date);
tooltip.innerHTML = `
<div class="font-bold">${date}</div>
<div>Weight: ${weight} kg</div>
`;
tooltip.style.left = context.tooltip.caretX + 'px';
tooltip.style.top = context.tooltip.caretY + 'px';
tooltip.style.opacity = 1;
}
}
},
interaction: {
intersect: false,
mode: 'index'
}
}
});
}
// Handle form submission
document.getElementById('weightForm').addEventListener('submit', function(e) {
e.preventDefault();
const dateInput = document.getElementById('date').value;
const weight = parseFloat(document.getElementById('weight').value);
// Validate date format DD/MM/YY
const dateRegex = /^(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[0-2])\/([0-9]{2})$/;
if (!dateRegex.test(dateInput)) {
alert('Please enter date in DD/MM/YY format (e.g. 25/12/23)');
return;
}
if (isNaN(weight) || weight <= 0) {
alert('Please enter a valid weight in kg');
return;
}
weightData.push({ date: dateInput, weight });
// Sort data chronologically after adding new entry
weightData.sort((a, b) => {
const [dayA, monthA, yearA] = a.date.split('/').map(Number);
const [dayB, monthB, yearB] = b.date.split('/').map(Number);
const dateA = new Date(2000 + yearA, monthA - 1, dayA);
const dateB = new Date(2000 + yearB, monthB - 1, dayB);
return dateA - dateB;
});
calculateChanges();
saveData(); // Save to localStorage
renderEntriesTable();
renderChart();
// Reset form
document.getElementById('weightForm').reset();
});
// Sign out function
function signOut() {
localStorage.removeItem('user');
// Force full page refresh to reset everything
window.location.reload();
}
// Show login modal
function showLogin() {
document.getElementById('loginModal').style.display = 'block';
document.getElementById('signupModal').style.display = 'none';
}
// Show signup modal
function showSignup() {
document.getElementById('loginModal').style.display = 'none';
document.getElementById('signupModal').style.display = 'block';
}
// Close login modal
function closeLogin() {
document.getElementById('loginModal').style.display = 'none';
}
// Close signup modal
function closeSignup() {
document.getElementById('signupModal').style.display = 'none';
}
// Initialize the app
calculateChanges();
renderEntriesTable();
renderChart();
// Time period buttons functionality
document.getElementById('weekBtn').addEventListener('click', function() {
// In a real app, this would filter data for the week view
alert('Week view selected. In a complete app, this would filter the data.');
});
document.getElementById('monthBtn').addEventListener('click', function() {
// In a real app, this would filter data for the month view
alert('Month view selected. In a complete app, this would filter the data.');
});
document.getElementById('yearBtn').addEventListener('click', function() {
// In a real app, this would filter data for the year view
alert('Year view selected. In a complete app, this would filter the data.');
});
</script>
</body>
</html>