Complete UI/UX overhaul: Polished login, fixed mood tracking, enhanced animations, and verified full-stack flows

This commit is contained in:
Gemini AI
2025-12-06 21:01:28 +04:00
Unverified
parent 0ba7af6489
commit 864070b26a
4 changed files with 593 additions and 82 deletions

View File

@@ -135,11 +135,21 @@ export const gratitudeAPI = {
// Progress API // Progress API
export const progressAPI = { export const progressAPI = {
async getProgressStats() { async getProgressStats() {
return await apiCall('/progress/stats'); return await apiCall('/dashboard/stats');
}, },
async getProgressHistory() { async getProgressHistory(days = 30) {
return await apiCall('/progress/history'); return await apiCall(`/progress?days=${days}`);
}
};
// Exercise API
export const exerciseAPI = {
async logSession(type, duration) {
return await apiCall('/exercises', {
method: 'POST',
body: JSON.stringify({ exerciseType: type, duration })
});
} }
}; };

View File

@@ -1,4 +1,4 @@
import { authAPI, moodAPI, thoughtAPI, gratitudeAPI, progressAPI, notificationAPI, isAuthenticated, initializeAPI } from './api.js'; import { authAPI, moodAPI, thoughtAPI, gratitudeAPI, progressAPI, notificationAPI, exerciseAPI, isAuthenticated, initializeAPI } from './api.js';
// Sound Manager using Web Audio API // Sound Manager using Web Audio API
class SoundManager { class SoundManager {
@@ -171,7 +171,7 @@ function showLoginModal() {
loginModal.innerHTML = ` loginModal.innerHTML = `
<div class="modal-overlay"> <div class="modal-overlay">
<div class="modal-card"> <div class="modal-card">
<h2>Welcome to MindShift</h2> <h2 class="auth-title">Welcome to MindShift</h2>
<form id="login-form"> <form id="login-form">
<div class="form-group"> <div class="form-group">
<label>Email</label> <label>Email</label>
@@ -227,7 +227,7 @@ async function handleLogin(e) {
document.getElementById('login-modal').remove(); document.getElementById('login-modal').remove();
location.reload(); location.reload();
} catch (error) { } catch (error) {
alert('Login failed: ' + error.message); showToast('Login failed: ' + error.message, 'error');
} }
} }
@@ -242,7 +242,7 @@ async function handleRegister(e) {
document.getElementById('login-modal').remove(); document.getElementById('login-modal').remove();
location.reload(); location.reload();
} catch (error) { } catch (error) {
alert('Registration failed: ' + error.message); showToast('Registration failed: ' + error.message, 'error');
} }
} }
@@ -276,25 +276,39 @@ async function updateIntensity(value) {
} }
async function saveMoodEntry() { async function saveMoodEntry() {
const moodType = localStorage.getItem('selectedMood'); // Try to get mood from DOM first (more reliable than localStorage if user just clicked)
const intensity = localStorage.getItem('moodIntensity'); const selectedCard = document.querySelector('.mood-card.selected');
let moodType = selectedCard ? selectedCard.dataset.mood : localStorage.getItem('selectedMood');
// Default intensity to 5 if not set
let intensity = localStorage.getItem('moodIntensity');
if (!intensity) {
const slider = document.querySelector('.slider');
intensity = slider ? slider.value : '5';
}
const notes = document.getElementById('moodNotes')?.value; const notes = document.getElementById('moodNotes')?.value;
if (!moodType || !intensity) { if (!moodType) {
alert('Please select a mood and intensity'); showToast('Please select a mood', 'warning');
return; return;
} }
try { try {
await moodAPI.trackMood(moodType, parseInt(intensity), notes); await moodAPI.trackMood(moodType, parseInt(intensity), notes);
triggerSuccessPing(); // Alive Feedback triggerSuccessPing(); // Alive Feedback
showSuccessMessage('Mood tracked successfully!'); showToast('Mood tracked successfully!');
// Clear form // Clear form
document.querySelectorAll('.mood-card').forEach(card => { document.querySelectorAll('.mood-card').forEach(card => {
card.classList.remove('selected'); card.classList.remove('selected');
}); });
// Reset slider
const slider = document.querySelector('.slider');
if (slider) slider.value = 5;
document.getElementById('intensityValue').textContent = '5'; document.getElementById('intensityValue').textContent = '5';
if (document.getElementById('moodNotes')) { if (document.getElementById('moodNotes')) {
document.getElementById('moodNotes').value = ''; document.getElementById('moodNotes').value = '';
} }
@@ -305,7 +319,7 @@ async function saveMoodEntry() {
// Update progress // Update progress
await updateProgress(); await updateProgress();
} catch (error) { } catch (error) {
alert('Failed to save mood: ' + error.message); showToast('Failed to save mood: ' + error.message, 'error');
} }
} }
@@ -317,7 +331,7 @@ async function saveThoughtRecord() {
const alternative = document.getElementById('alternative')?.value; const alternative = document.getElementById('alternative')?.value;
if (!situation || !thoughts) { if (!situation || !thoughts) {
alert('Please fill in at least the situation and thoughts'); showToast('Please fill in at least the situation and thoughts', 'warning');
return; return;
} }
@@ -342,13 +356,13 @@ async function saveThoughtRecord() {
}); });
triggerSuccessPing(); // Alive Feedback triggerSuccessPing(); // Alive Feedback
showSuccessMessage('Thought record saved successfully!'); showToast('Thought record saved successfully!');
closeExercise('thought-record-exercise'); closeExercise('thought-record-exercise');
// Update progress // Update progress
await updateProgress(); await updateProgress();
} catch (error) { } catch (error) {
alert('Failed to save thought record: ' + error.message); showToast('Failed to save thought record: ' + error.message, 'error');
} }
} }
@@ -362,7 +376,7 @@ async function saveGratitudeEntry() {
}); });
if (entries.length === 0) { if (entries.length === 0) {
alert('Please add at least one gratitude entry'); showToast('Please add at least one gratitude entry', 'warning');
return; return;
} }
@@ -373,13 +387,13 @@ async function saveGratitudeEntry() {
}); });
triggerSuccessPing(); // Alive Feedback triggerSuccessPing(); // Alive Feedback
showSuccessMessage('Gratitude entries saved successfully!'); showToast('Gratitude entries saved successfully!');
closeExercise('gratitude-exercise'); closeExercise('gratitude-exercise');
// Update progress // Update progress
await updateProgress(); await updateProgress();
} catch (error) { } catch (error) {
alert('Failed to save gratitude entries: ' + error.message); showToast('Failed to save gratitude entries: ' + error.message, 'error');
} }
} }
@@ -407,20 +421,20 @@ async function updateProgress() {
if (progressContainer) { if (progressContainer) {
progressContainer.innerHTML = ` progressContainer.innerHTML = `
<div class="progress-card"> <div class="progress-card">
<div class="progress-value">${stats.dayStreak || 0}</div> <div class="progress-value">${stats.today.mood_score || '-'}</div>
<div class="progress-label">Day Streak</div> <div class="progress-label">Today's Mood</div>
</div> </div>
<div class="progress-card"> <div class="progress-card">
<div class="progress-value">${stats.totalSessions || 0}</div> <div class="progress-value">${stats.totals.totalSessions || 0}</div>
<div class="progress-label">Sessions</div> <div class="progress-label">Sessions</div>
</div> </div>
<div class="progress-card"> <div class="progress-card">
<div class="progress-value">${stats.improvement || 0}%</div> <div class="progress-value">${Math.round(stats.week.avgMood * 10) / 10 || '-'}</div>
<div class="progress-label">Improvement</div> <div class="progress-label">Weekly Avg</div>
</div> </div>
<div class="progress-card"> <div class="progress-card">
<div class="progress-value">${stats.avgMood || 0}</div> <div class="progress-value">${stats.totals.totalGratitude || 0}</div>
<div class="progress-label">Avg Mood</div> <div class="progress-label">Gratitude</div>
</div> </div>
`; `;
} }
@@ -466,8 +480,71 @@ window.handleRegister = handleRegister;
window.deleteNotification = deleteNotification; window.deleteNotification = deleteNotification;
window.markNotificationAsRead = markNotificationAsRead; window.markNotificationAsRead = markNotificationAsRead;
window.addNotification = addNotification; window.addNotification = addNotification;
window.logout = logout;
window.toggleNotifications = toggleNotifications;
window.renderHistory = renderHistory;
// Logout function
async function logout() {
try {
await authAPI.logout();
location.reload();
} catch (error) {
console.error('Logout failed:', error);
location.reload(); // Reload anyway to clear state
}
}
// Toggle Notifications
function toggleNotifications() {
const list = document.getElementById('notification-list');
if (list) {
list.style.display = list.style.display === 'block' ? 'none' : 'block';
}
}
// Enhanced Toast Notification
function showToast(message, type = 'success') {
const toast = document.createElement('div');
toast.className = 'success-message'; // Keep class for animation
toast.textContent = message;
const bgColor = type === 'error' ? 'var(--error)' :
type === 'warning' ? 'var(--warning)' :
'var(--success)';
toast.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: ${bgColor};
color: white;
padding: 16px 24px;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0,0,0,0.2);
z-index: 2100;
animation: slideInDown 0.3s ease;
font-weight: 500;
display: flex;
align-items: center;
gap: 8px;
`;
// Add icon based on type
const icon = type === 'error' ? '⚠️' : type === 'warning' ? '🔔' : '✅';
toast.innerHTML = `<span style="font-size: 1.2em">${icon}</span> ${message}`;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.animation = 'fadeOut 0.3s ease forwards';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
// Replace showSuccessMessage with showToast alias for backward compatibility
const showSuccessMessage = (msg) => showToast(msg, 'success');
// Keep existing utility functions
function createParticles() { function createParticles() {
const particlesContainer = document.getElementById('particles'); const particlesContainer = document.getElementById('particles');
if (!particlesContainer) return; if (!particlesContainer) return;
@@ -522,29 +599,6 @@ function initializeBeliefSlider() {
} }
} }
function showSuccessMessage(message) {
const successDiv = document.createElement('div');
successDiv.className = 'success-message';
successDiv.textContent = message;
successDiv.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: var(--success);
color: white;
padding: 16px 24px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 2000;
animation: slideIn 0.3s ease;
`;
document.body.appendChild(successDiv);
setTimeout(() => {
successDiv.remove();
}, 3000);
}
function drawWeeklyChart(history) { function drawWeeklyChart(history) {
const canvas = document.getElementById('weeklyChart'); const canvas = document.getElementById('weeklyChart');
if (!canvas || !history) return; if (!canvas || !history) return;
@@ -558,23 +612,22 @@ function drawWeeklyChart(history) {
// Get last 7 days from history // Get last 7 days from history
const days = []; const days = [];
const counts = []; const scores = [];
for (let i = 6; i >= 0; i--) { for (let i = 6; i >= 0; i--) {
const date = new Date(); const date = new Date();
date.setDate(date.getDate() - i); date.setDate(date.getDate() - i);
const dateStr = date.toDateString(); const dateStr = date.toISOString().split('T')[0];
const dayCount = history.filter(entry => const dayEntry = history.find(entry => entry.date === dateStr);
new Date(entry.date).toDateString() === dateStr const score = dayEntry ? dayEntry.mood_score : 0;
).length;
days.push(date.toLocaleDateString('en', { weekday: 'short' })); days.push(date.toLocaleDateString('en', { weekday: 'short' }));
counts.push(dayCount); scores.push(score);
} }
// Draw chart // Draw chart
const maxValue = Math.max(...counts, 5); const maxValue = 10; // Mood intensity max is 10
const barWidth = width / 7 * 0.6; const barWidth = width / 7 * 0.6;
const spacing = width / 7; const spacing = width / 7;
@@ -582,23 +635,26 @@ function drawWeeklyChart(history) {
ctx.font = '12px sans-serif'; ctx.font = '12px sans-serif';
ctx.textAlign = 'center'; ctx.textAlign = 'center';
counts.forEach((count, index) => { scores.forEach((score, index) => {
const barHeight = (count / maxValue) * (height - 40); const barHeight = (score / maxValue) * (height - 40);
const x = index * spacing + (spacing - barWidth) / 2; const x = index * spacing + (spacing - barWidth) / 2;
const y = height - barHeight - 20; const y = height - barHeight - 20;
// Draw bar // Draw bar
ctx.fillRect(x, y, barWidth, barHeight); // Color based on score
if (score >= 7) ctx.fillStyle = '#66BB6A'; // Green
else if (score >= 4) ctx.fillStyle = '#FFB74D'; // Orange
else ctx.fillStyle = '#FF5252'; // Red
if (score > 0) {
ctx.fillRect(x, y, barWidth, barHeight);
ctx.fillStyle = '#666';
ctx.fillText(score, x + barWidth / 2, y - 5);
}
// Draw day label // Draw day label
ctx.fillStyle = '#666'; ctx.fillStyle = '#666';
ctx.fillText(days[index], x + barWidth / 2, height - 5); ctx.fillText(days[index], x + barWidth / 2, height - 5);
// Draw count
if (count > 0) {
ctx.fillStyle = '#FF6B6B';
ctx.fillText(count, x + barWidth / 2, y - 5);
}
}); });
} }
@@ -732,6 +788,109 @@ async function addNotification(title, message, type = 'info') {
} }
} }
// Render History
async function renderHistory(type) {
const container = document.getElementById('history-container');
if (!container) return;
container.innerHTML = '<div class="spinner"></div>';
try {
let data = [];
let content = '';
switch(type) {
case 'mood':
data = await moodAPI.getMoodHistory();
if (data.length === 0) {
content = '<div class="empty-state">No mood entries yet. Start tracking! 📝</div>';
} else {
content = data.map(entry => `
<div class="history-item" style="border-left: 4px solid var(--${entry.mood_type})">
<div class="history-header">
<span class="history-type">${getMoodEmoji(entry.mood_type)} ${capitalize(entry.mood_type)}</span>
<span class="history-date">${formatDate(entry.created_at)}</span>
</div>
<div class="history-details">
Intensity: <strong>${entry.intensity}/10</strong>
${entry.notes ? `<p class="history-notes">"${entry.notes}"</p>` : ''}
</div>
</div>
`).join('');
}
break;
case 'thoughts':
data = await thoughtAPI.getThoughtRecords();
if (data.length === 0) {
content = '<div class="empty-state">No thought records yet. 🧠</div>';
} else {
content = data.map(entry => `
<div class="history-item" style="border-left: 4px solid var(--primary)">
<div class="history-header">
<span class="history-type">Thought Record</span>
<span class="history-date">${formatDate(entry.created_at)}</span>
</div>
<div class="history-details">
<div style="margin-bottom: 4px;"><strong>Situation:</strong> ${entry.situation}</div>
<div style="margin-bottom: 4px;"><strong>Thought:</strong> ${entry.automatic_thought}</div>
<div><strong>Emotion:</strong> ${entry.emotion} (${entry.emotion_intensity}%)</div>
</div>
</div>
`).join('');
}
break;
case 'gratitude':
data = await gratitudeAPI.getGratitudeEntries();
if (data.length === 0) {
content = '<div class="empty-state">No gratitude entries yet. 🙏</div>';
} else {
content = data.map(entry => `
<div class="history-item" style="border-left: 4px solid var(--joy)">
<div class="history-header">
<span class="history-type">Gratitude</span>
<span class="history-date">${formatDate(entry.created_at)}</span>
</div>
<div class="history-details">
<p class="history-notes">"${entry.entry}"</p>
</div>
</div>
`).join('');
}
break;
}
container.innerHTML = content;
// Animate items
document.querySelectorAll('.history-item').forEach((item, index) => {
item.style.opacity = '0';
item.style.animation = `slideInUp 0.3s ease forwards ${index * 0.05}s`;
});
} catch (error) {
container.innerHTML = `<div class="error-state">Failed to load history: ${error.message}</div>`;
}
}
function getMoodEmoji(type) {
const map = {
'joy': '😊', 'peace': '😌', 'energy': '⚡',
'anxiety': '😰', 'sadness': '😢', 'anger': '😠'
};
return map[type] || '😐';
}
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
function formatDate(dateStr) {
const date = new Date(dateStr);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
}
// Render Dynamic Content // Render Dynamic Content
function showSection(sectionId) { function showSection(sectionId) {
const mainContent = document.getElementById('main-content'); const mainContent = document.getElementById('main-content');
@@ -772,7 +931,7 @@ function showSection(sectionId) {
<!-- Stats loaded dynamically --> <!-- Stats loaded dynamically -->
<div class="progress-container"> <div class="progress-container">
<div class="progress-card"> <div class="progress-card">
<div class="progress-value">Loading...</div> <div class="progress-value"><div class="spinner" style="width: 24px; height: 24px; border-width: 3px;"></div></div>
</div> </div>
</div> </div>
</div> </div>
@@ -800,27 +959,27 @@ function showSection(sectionId) {
<div class="card" style="animation: fadeIn 0.5s;"> <div class="card" style="animation: fadeIn 0.5s;">
<h2 class="card-title">How are you feeling?</h2> <h2 class="card-title">How are you feeling?</h2>
<div class="mood-grid"> <div class="mood-grid">
<div class="mood-card" onclick="selectMood(this, 'joy')"> <div class="mood-card" data-mood="joy" onclick="selectMood(this, 'joy')">
<span class="mood-emoji">😊</span> <span class="mood-emoji">😊</span>
<span class="mood-label">Joy</span> <span class="mood-label">Joy</span>
</div> </div>
<div class="mood-card" onclick="selectMood(this, 'peace')"> <div class="mood-card" data-mood="peace" onclick="selectMood(this, 'peace')">
<span class="mood-emoji">😌</span> <span class="mood-emoji">😌</span>
<span class="mood-label">Peace</span> <span class="mood-label">Peace</span>
</div> </div>
<div class="mood-card" onclick="selectMood(this, 'energy')"> <div class="mood-card" data-mood="energy" onclick="selectMood(this, 'energy')">
<span class="mood-emoji">⚡</span> <span class="mood-emoji">⚡</span>
<span class="mood-label">Energy</span> <span class="mood-label">Energy</span>
</div> </div>
<div class="mood-card" onclick="selectMood(this, 'anxiety')"> <div class="mood-card" data-mood="anxiety" onclick="selectMood(this, 'anxiety')">
<span class="mood-emoji">😰</span> <span class="mood-emoji">😰</span>
<span class="mood-label">Anxiety</span> <span class="mood-label">Anxiety</span>
</div> </div>
<div class="mood-card" onclick="selectMood(this, 'sadness')"> <div class="mood-card" data-mood="sadness" onclick="selectMood(this, 'sadness')">
<span class="mood-emoji">😢</span> <span class="mood-emoji">😢</span>
<span class="mood-label">Sadness</span> <span class="mood-label">Sadness</span>
</div> </div>
<div class="mood-card" onclick="selectMood(this, 'anger')"> <div class="mood-card" data-mood="anger" onclick="selectMood(this, 'anger')">
<span class="mood-emoji">😠</span> <span class="mood-emoji">😠</span>
<span class="mood-label">Anger</span> <span class="mood-label">Anger</span>
</div> </div>
@@ -906,7 +1065,7 @@ function showSection(sectionId) {
<h2 class="card-title">Your Progress 📈</h2> <h2 class="card-title">Your Progress 📈</h2>
<div id="progress-stats" class="progress-container"> <div id="progress-stats" class="progress-container">
<!-- Stats will be loaded here --> <!-- Stats will be loaded here -->
<div class="progress-card"><div class="progress-value">...</div></div> <div class="progress-card"><div class="progress-value"><div class="spinner" style="width: 24px; height: 24px; border-width: 3px;"></div></div></div>
</div> </div>
</div> </div>
<div class="card" style="animation: slideInUp 0.6s;"> <div class="card" style="animation: slideInUp 0.6s;">
@@ -915,8 +1074,22 @@ function showSection(sectionId) {
<canvas id="weeklyChart"></canvas> <canvas id="weeklyChart"></canvas>
</div> </div>
</div> </div>
<div class="card" style="animation: slideInUp 0.7s;">
<h2 class="card-title">Recent History 📜</h2>
<div class="tabs" style="display: flex; gap: 10px; margin-bottom: 20px;">
<button class="btn btn-secondary btn-sm" onclick="renderHistory('mood')">Moods</button>
<button class="btn btn-secondary btn-sm" onclick="renderHistory('thoughts')">Thoughts</button>
<button class="btn btn-secondary btn-sm" onclick="renderHistory('gratitude')">Gratitude</button>
</div>
<div id="history-container" class="history-list">
<!-- History items will be loaded here -->
<div style="text-align: center; color: var(--on-surface-variant);">Select a category to view history</div>
</div>
</div>
`; `;
updateProgress(); updateProgress();
renderHistory('mood'); // Default to mood
break; break;
} }
} }
@@ -978,13 +1151,22 @@ function quickRelax() {
<p style="font-size: 18px; margin-bottom: 30px;">${randomTechnique.desc}</p> <p style="font-size: 18px; margin-bottom: 30px;">${randomTechnique.desc}</p>
<div class="exercise-actions" style="justify-content: center;"> <div class="exercise-actions" style="justify-content: center;">
<button class="btn btn-primary" onclick="this.closest('.exercise-modal').remove(); quickRelax()">Try Another</button> <button class="btn btn-primary" onclick="this.closest('.exercise-modal').remove(); quickRelax()">Try Another</button>
<button class="btn btn-secondary" onclick="this.closest('.exercise-modal').remove()">Done</button> <button class="btn btn-secondary" onclick="finishRelaxSession(this)">Done</button>
</div> </div>
</div> </div>
`; `;
document.body.appendChild(modal); document.body.appendChild(modal);
} }
function finishRelaxSession(btn) {
btn.closest('.exercise-modal').remove();
exerciseAPI.logSession('relaxation', 120).then(() => {
triggerSuccessPing();
showSuccessMessage('Relaxation session logged! 🧘');
updateProgress();
});
}
function breathingExercise() { function breathingExercise() {
startBreathing(); startBreathing();
} }
@@ -1089,9 +1271,16 @@ function stopBreathingExercise() {
clearInterval(breathingInterval); clearInterval(breathingInterval);
const circle = document.getElementById('breathing-circle'); const circle = document.getElementById('breathing-circle');
const text = document.getElementById('breathing-text'); const text = document.getElementById('breathing-text');
circle.classList.remove('inhale', 'exhale'); if (circle) circle.classList.remove('inhale', 'exhale');
text.textContent = 'Ready'; if (text) text.textContent = 'Ready';
breathingPhase = 'inhale'; breathingPhase = 'inhale';
// Log session (assuming ~1 min or track actual time)
exerciseAPI.logSession('breathing', 60).then(() => {
triggerSuccessPing();
showSuccessMessage('Breathing session logged! 🌬️');
updateProgress();
});
} }
// Export additional functions // Export additional functions
@@ -1103,4 +1292,5 @@ window.deleteNotification = deleteNotification;
window.quickRelax = quickRelax; window.quickRelax = quickRelax;
window.startBreathing = breathingExercise; // Alias for startBreathing in HTML window.startBreathing = breathingExercise; // Alias for startBreathing in HTML
window.showQuickActionMenu = showQuickActionMenu; window.showQuickActionMenu = showQuickActionMenu;
window.navigateTo = showSection; // Alias for navigateTo in HTML window.navigateTo = showSection; // Alias for navigateTo in HTML
window.finishRelaxSession = finishRelaxSession;

View File

@@ -667,8 +667,286 @@ textarea:focus, input[type="text"]:focus {
.nav-label { font-size: 10px; } .nav-label { font-size: 10px; }
} }
/* Exercise Modals */
.exercise-modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
max-width: 500px;
max-height: 90vh;
overflow-y: auto;
z-index: 2000;
animation: zoomIn 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
.exercise-modal .card {
margin: 0;
box-shadow: 0 20px 60px rgba(0,0,0,0.4);
border: 2px solid rgba(255, 255, 255, 0.8);
}
@keyframes zoomIn {
from { opacity: 0; transform: translate(-50%, -50%) scale(0.8); }
to { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
.exercise-modal h3 {
color: var(--primary);
margin-bottom: 24px;
font-size: 24px;
}
.exercise-actions {
display: flex;
gap: 12px;
margin-top: 24px;
justify-content: flex-end;
}
/* Desktop Sidebar */ /* Desktop Sidebar */
@media (min-width: 1024px) { @media (min-width: 1024px) {
.bottom-nav { display: none; } .bottom-nav {
/* ... (keep existing desktop styles if needed, but adapt to new look) ... */ display: flex;
flex-direction: column;
top: 80px;
left: 20px;
right: auto;
bottom: 20px;
width: 100px;
height: auto;
border-radius: 20px;
justify-content: flex-start;
padding-top: 40px;
gap: 20px;
background: rgba(255, 255, 255, 0.85);
}
.main-content {
margin-left: 120px;
max-width: calc(100% - 140px);
}
.nav-item.active::after {
left: 0;
top: 50%;
transform: translateY(-50%);
bottom: auto;
width: 4px;
height: 20px;
border-radius: 4px;
}
}
/* History Lists */
.history-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.history-item {
background: rgba(255, 255, 255, 0.5);
border-radius: 12px;
padding: 16px;
transition: all 0.3s ease;
}
.history-item:hover {
background: rgba(255, 255, 255, 0.8);
transform: translateX(5px);
}
.history-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.history-type {
font-weight: bold;
font-size: 16px;
}
.history-date {
font-size: 12px;
color: var(--on-surface-variant);
}
.history-details {
font-size: 14px;
color: var(--on-surface);
}
.history-notes {
font-style: italic;
color: var(--on-surface-variant);
margin-top: 4px;
}
.empty-state {
text-align: center;
padding: 40px;
color: var(--on-surface-variant);
font-size: 18px;
}
.error-state {
text-align: center;
padding: 20px;
color: var(--error);
background: rgba(255, 82, 82, 0.1);
border-radius: 12px;
}
.btn-sm {
padding: 8px 16px;
font-size: 14px;
}
/* Loading Spinner */
.spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(0,0,0,0.1);
border-left-color: var(--primary);
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 20px auto;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Auth Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(8px);
display: flex;
justify-content: center;
align-items: center;
z-index: 3000;
animation: fadeIn 0.3s ease;
}
.modal-card {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(20px);
padding: 40px;
border-radius: 30px;
width: 90%;
max-width: 400px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.8);
text-align: center;
animation: slideInUp 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
overflow: hidden;
}
/* Animated gradient border top */
.modal-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 6px;
background: linear-gradient(90deg, var(--primary), var(--secondary), var(--tertiary));
animation: gradientFlow 3s linear infinite;
background-size: 200% 100%;
}
@keyframes gradientFlow {
0% { background-position: 100% 0; }
100% { background-position: -100% 0; }
}
.auth-title {
font-size: 32px;
font-weight: 800;
margin-bottom: 30px;
background: linear-gradient(45deg, var(--primary), var(--secondary));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: pulseText 3s infinite ease-in-out;
}
@keyframes pulseText {
0%, 100% { transform: scale(1); filter: brightness(100%); }
50% { transform: scale(1.05); filter: brightness(110%); }
}
.form-group {
margin-bottom: 20px;
text-align: left;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: var(--on-surface-variant);
font-weight: 500;
font-size: 14px;
margin-left: 4px;
}
.form-input {
width: 100%;
padding: 16px;
border-radius: 16px;
border: 2px solid rgba(0,0,0,0.05);
background: rgba(255, 255, 255, 0.5);
font-size: 16px;
transition: all 0.3s ease;
}
.form-input:focus {
background: white;
border-color: var(--primary);
box-shadow: 0 0 0 4px rgba(255, 107, 107, 0.1);
transform: translateY(-2px);
}
.switch-form {
margin-top: 24px;
font-size: 14px;
color: var(--on-surface-variant);
}
.switch-form a {
color: var(--primary);
text-decoration: none;
font-weight: 700;
margin-left: 4px;
position: relative;
}
.switch-form a::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
width: 100%;
height: 2px;
background: var(--primary);
transform: scaleX(0);
transition: transform 0.3s ease;
transform-origin: right;
}
.switch-form a:hover::after {
transform: scaleX(1);
transform-origin: left;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
} }

View File

@@ -98,6 +98,17 @@ db.serialize(() => {
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id) FOREIGN KEY (user_id) REFERENCES users (id)
)`); )`);
// Exercise Logs table
db.run(`CREATE TABLE IF NOT EXISTS exercise_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
exercise_type TEXT NOT NULL,
duration INTEGER NOT NULL,
completed BOOLEAN DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id)
)`);
}); });
// JWT authentication middleware // JWT authentication middleware
@@ -376,6 +387,27 @@ app.post('/api/gratitude', authenticateToken, (req, res) => {
} }
}); });
// Log Exercise Session
app.post('/api/exercises', authenticateToken, (req, res) => {
try {
const { exerciseType, duration } = req.body;
if (!exerciseType || !duration) {
return res.status(400).json({ error: 'Type and duration required' });
}
db.run('INSERT INTO exercise_logs (user_id, exercise_type, duration) VALUES (?, ?, ?)',
[req.user.id, exerciseType, duration], function(err) {
if (err) {
return res.status(500).json({ error: 'Database error' });
}
res.status(201).json({ success: true, id: this.lastID });
});
} catch (error) {
res.status(500).json({ error: 'Server error' });
}
});
// Get gratitude entries // Get gratitude entries
app.get('/api/gratitude', authenticateToken, (req, res) => { app.get('/api/gratitude', authenticateToken, (req, res) => {
const limit = parseInt(req.query.limit) || 30; const limit = parseInt(req.query.limit) || 30;
@@ -427,8 +459,9 @@ app.get('/api/dashboard/stats', authenticateToken, (req, res) => {
db.get(`SELECT db.get(`SELECT
(SELECT COUNT(*) FROM mood_entries WHERE user_id = ?) as totalMoods, (SELECT COUNT(*) FROM mood_entries WHERE user_id = ?) as totalMoods,
(SELECT COUNT(*) FROM thoughts WHERE user_id = ?) as totalThoughts, (SELECT COUNT(*) FROM thoughts WHERE user_id = ?) as totalThoughts,
(SELECT COUNT(*) FROM gratitude_entries WHERE user_id = ?) as totalGratitude`, (SELECT COUNT(*) FROM gratitude_entries WHERE user_id = ?) as totalGratitude,
[req.user.id, req.user.id, req.user.id], (err, totals) => { (SELECT COUNT(*) FROM exercise_logs WHERE user_id = ?) as totalSessions`,
[req.user.id, req.user.id, req.user.id, req.user.id], (err, totals) => {
if (err) { if (err) {
return res.status(500).json({ error: 'Database error' }); return res.status(500).json({ error: 'Database error' });
} }