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

@@ -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
class SoundManager {
@@ -171,7 +171,7 @@ function showLoginModal() {
loginModal.innerHTML = `
<div class="modal-overlay">
<div class="modal-card">
<h2>Welcome to MindShift</h2>
<h2 class="auth-title">Welcome to MindShift</h2>
<form id="login-form">
<div class="form-group">
<label>Email</label>
@@ -227,7 +227,7 @@ async function handleLogin(e) {
document.getElementById('login-modal').remove();
location.reload();
} 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();
location.reload();
} catch (error) {
alert('Registration failed: ' + error.message);
showToast('Registration failed: ' + error.message, 'error');
}
}
@@ -276,25 +276,39 @@ async function updateIntensity(value) {
}
async function saveMoodEntry() {
const moodType = localStorage.getItem('selectedMood');
const intensity = localStorage.getItem('moodIntensity');
// Try to get mood from DOM first (more reliable than localStorage if user just clicked)
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;
if (!moodType || !intensity) {
alert('Please select a mood and intensity');
if (!moodType) {
showToast('Please select a mood', 'warning');
return;
}
try {
await moodAPI.trackMood(moodType, parseInt(intensity), notes);
triggerSuccessPing(); // Alive Feedback
showSuccessMessage('Mood tracked successfully!');
showToast('Mood tracked successfully!');
// Clear form
document.querySelectorAll('.mood-card').forEach(card => {
card.classList.remove('selected');
});
// Reset slider
const slider = document.querySelector('.slider');
if (slider) slider.value = 5;
document.getElementById('intensityValue').textContent = '5';
if (document.getElementById('moodNotes')) {
document.getElementById('moodNotes').value = '';
}
@@ -305,7 +319,7 @@ async function saveMoodEntry() {
// Update progress
await updateProgress();
} 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;
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;
}
@@ -342,13 +356,13 @@ async function saveThoughtRecord() {
});
triggerSuccessPing(); // Alive Feedback
showSuccessMessage('Thought record saved successfully!');
showToast('Thought record saved successfully!');
closeExercise('thought-record-exercise');
// Update progress
await updateProgress();
} 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) {
alert('Please add at least one gratitude entry');
showToast('Please add at least one gratitude entry', 'warning');
return;
}
@@ -373,13 +387,13 @@ async function saveGratitudeEntry() {
});
triggerSuccessPing(); // Alive Feedback
showSuccessMessage('Gratitude entries saved successfully!');
showToast('Gratitude entries saved successfully!');
closeExercise('gratitude-exercise');
// Update progress
await updateProgress();
} 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) {
progressContainer.innerHTML = `
<div class="progress-card">
<div class="progress-value">${stats.dayStreak || 0}</div>
<div class="progress-label">Day Streak</div>
<div class="progress-value">${stats.today.mood_score || '-'}</div>
<div class="progress-label">Today's Mood</div>
</div>
<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>
<div class="progress-card">
<div class="progress-value">${stats.improvement || 0}%</div>
<div class="progress-label">Improvement</div>
<div class="progress-value">${Math.round(stats.week.avgMood * 10) / 10 || '-'}</div>
<div class="progress-label">Weekly Avg</div>
</div>
<div class="progress-card">
<div class="progress-value">${stats.avgMood || 0}</div>
<div class="progress-label">Avg Mood</div>
<div class="progress-value">${stats.totals.totalGratitude || 0}</div>
<div class="progress-label">Gratitude</div>
</div>
`;
}
@@ -466,8 +480,71 @@ window.handleRegister = handleRegister;
window.deleteNotification = deleteNotification;
window.markNotificationAsRead = markNotificationAsRead;
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() {
const particlesContainer = document.getElementById('particles');
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) {
const canvas = document.getElementById('weeklyChart');
if (!canvas || !history) return;
@@ -558,23 +612,22 @@ function drawWeeklyChart(history) {
// Get last 7 days from history
const days = [];
const counts = [];
const scores = [];
for (let i = 6; i >= 0; i--) {
const date = new Date();
date.setDate(date.getDate() - i);
const dateStr = date.toDateString();
const dateStr = date.toISOString().split('T')[0];
const dayCount = history.filter(entry =>
new Date(entry.date).toDateString() === dateStr
).length;
const dayEntry = history.find(entry => entry.date === dateStr);
const score = dayEntry ? dayEntry.mood_score : 0;
days.push(date.toLocaleDateString('en', { weekday: 'short' }));
counts.push(dayCount);
scores.push(score);
}
// Draw chart
const maxValue = Math.max(...counts, 5);
const maxValue = 10; // Mood intensity max is 10
const barWidth = width / 7 * 0.6;
const spacing = width / 7;
@@ -582,23 +635,26 @@ function drawWeeklyChart(history) {
ctx.font = '12px sans-serif';
ctx.textAlign = 'center';
counts.forEach((count, index) => {
const barHeight = (count / maxValue) * (height - 40);
scores.forEach((score, index) => {
const barHeight = (score / maxValue) * (height - 40);
const x = index * spacing + (spacing - barWidth) / 2;
const y = height - barHeight - 20;
// 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
ctx.fillStyle = '#666';
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
function showSection(sectionId) {
const mainContent = document.getElementById('main-content');
@@ -772,7 +931,7 @@ function showSection(sectionId) {
<!-- Stats loaded dynamically -->
<div class="progress-container">
<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>
@@ -800,27 +959,27 @@ function showSection(sectionId) {
<div class="card" style="animation: fadeIn 0.5s;">
<h2 class="card-title">How are you feeling?</h2>
<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-label">Joy</span>
</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-label">Peace</span>
</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-label">Energy</span>
</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-label">Anxiety</span>
</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-label">Sadness</span>
</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-label">Anger</span>
</div>
@@ -906,7 +1065,7 @@ function showSection(sectionId) {
<h2 class="card-title">Your Progress 📈</h2>
<div id="progress-stats" class="progress-container">
<!-- 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 class="card" style="animation: slideInUp 0.6s;">
@@ -915,8 +1074,22 @@ function showSection(sectionId) {
<canvas id="weeklyChart"></canvas>
</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();
renderHistory('mood'); // Default to mood
break;
}
}
@@ -978,13 +1151,22 @@ function quickRelax() {
<p style="font-size: 18px; margin-bottom: 30px;">${randomTechnique.desc}</p>
<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-secondary" onclick="this.closest('.exercise-modal').remove()">Done</button>
<button class="btn btn-secondary" onclick="finishRelaxSession(this)">Done</button>
</div>
</div>
`;
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() {
startBreathing();
}
@@ -1089,9 +1271,16 @@ function stopBreathingExercise() {
clearInterval(breathingInterval);
const circle = document.getElementById('breathing-circle');
const text = document.getElementById('breathing-text');
circle.classList.remove('inhale', 'exhale');
text.textContent = 'Ready';
if (circle) circle.classList.remove('inhale', 'exhale');
if (text) text.textContent = 'Ready';
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
@@ -1103,4 +1292,5 @@ window.deleteNotification = deleteNotification;
window.quickRelax = quickRelax;
window.startBreathing = breathingExercise; // Alias for startBreathing in HTML
window.showQuickActionMenu = showQuickActionMenu;
window.navigateTo = showSection; // Alias for navigateTo in HTML
window.navigateTo = showSection; // Alias for navigateTo in HTML
window.finishRelaxSession = finishRelaxSession;