feat: US-011 - Frontend - Skip, lives display, and delete confirmation

This commit is contained in:
Echo
2026-02-10 17:07:25 +00:00
parent 5ed8680164
commit 8897de25ed
2 changed files with 262 additions and 5 deletions

View File

@@ -198,10 +198,37 @@
.habit-card-lives {
display: flex;
justify-content: center;
gap: var(--space-1);
align-items: center;
gap: var(--space-2);
font-size: var(--text-lg);
}
.habit-card-lives-hearts {
display: flex;
gap: var(--space-1);
}
.habit-card-skip-btn {
background: none;
border: none;
color: var(--text-muted);
font-size: var(--text-xs);
cursor: pointer;
padding: var(--space-1) var(--space-2);
border-radius: var(--radius-sm);
transition: all var(--transition-base);
}
.habit-card-skip-btn:hover:not(:disabled) {
background: var(--bg-hover);
color: var(--text-primary);
}
.habit-card-skip-btn:disabled {
cursor: not-allowed;
opacity: 0.3;
}
.habit-card-completion {
font-size: var(--text-sm);
color: var(--text-muted);
@@ -811,6 +838,8 @@
throw new Error(`HTTP ${response.status}`);
}
habits = await response.json();
// Cache habits for delete confirmation
localStorage.setItem('habitsCache', JSON.stringify(habits));
renderHabits();
} catch (error) {
console.error('Failed to load habits:', error);
@@ -900,7 +929,17 @@
<div class="habit-card-last-check">${lastCheckInfo}</div>
<div class="habit-card-lives">${livesHtml}</div>
<div class="habit-card-lives">
<div class="habit-card-lives-hearts">${livesHtml}</div>
<button
class="habit-card-skip-btn"
onclick="skipHabitDay('${habit.id}', '${escapeHtml(habit.name)}')"
${habit.lives === 0 ? 'disabled' : ''}
title="${habit.lives === 0 ? 'No lives left' : 'Skip today and use 1 life'}"
>
Skip day
</button>
</div>
<div class="habit-card-completion">${completionRate}% (30d)</div>
@@ -1335,10 +1374,66 @@
// Delete habit (placeholder)
async function deleteHabit(habitId) {
if (!confirm('Are you sure you want to delete this habit?')) {
// Find habit to get its name for confirmation
const habits = JSON.parse(localStorage.getItem('habitsCache') || '[]');
const habit = habits.find(h => h.id === habitId);
const habitName = habit ? habit.name : 'this habit';
if (!confirm(`Delete ${habitName}? This cannot be undone.`)) {
return;
}
alert('Delete functionality - coming in next story!');
try {
const response = await fetch(`/echo/api/habits/${habitId}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
// Show success toast
showToast('Habit deleted', 'success');
// Refresh habits list (will remove card from DOM)
await loadHabits();
} catch (error) {
console.error('Failed to delete habit:', error);
showToast(`Failed to delete habit: ${error.message}`, 'error');
}
}
// Skip a habit day using a life
async function skipHabitDay(habitId, habitName) {
if (!confirm('Use 1 life to skip today?')) {
return;
}
try {
const response = await fetch(`/echo/api/habits/${habitId}/skip`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error || `HTTP ${response.status}`);
}
const updatedHabit = await response.json();
// Show success toast with remaining lives
const remainingLives = updatedHabit.lives;
showToast(`Day skipped. ${remainingLives} ${remainingLives === 1 ? 'life' : 'lives'} remaining.`, 'success');
// Refresh habits list
await loadHabits();
} catch (error) {
console.error('Failed to skip habit day:', error);
showToast(`Failed to skip: ${error.message}`, 'error');
}
}
// Check-in state