Your profile information and study statistics will update automatically as you use StudyPlan.
@@ -807,10 +786,7 @@ function formatDate(dateStr) {
async function downloadData() {
try {
const response = await fetch('/api/download');
-
- if (!response.ok) {
- throw new Error('Failed to download data');
- }
+ if (!response.ok) throw new Error('Failed to download data');
const blob = await response.blob();
const url = URL.createObjectURL(blob);
@@ -821,35 +797,30 @@ async function downloadData() {
a.click();
document.body.removeChild(a);
setTimeout(() => URL.revokeObjectURL(url), 100);
-
} catch (error) {
- console.error(error);
- Toast.show('Failed to download data', 'error');
+ console.error(error);
+ Toast.show('Failed to download data', 'error');
}
}
async function downloadCalendar() {
- try {
- const response = await fetch('/api/download/calendar');
-
- if (!response.ok) {
- throw new Error('Failed to export calendar');
- }
+ try {
+ const response = await fetch('/api/download/calendar');
+ if (!response.ok) throw new Error('Failed to export calendar');
- const blob = await response.blob();
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = 'studyplan_calendar.ics';
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- setTimeout(() => URL.revokeObjectURL(url), 100);
-
- } catch (error) {
- console.error(error);
- alert('Failed to export calendar');
- }
+ const blob = await response.blob();
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = 'studyplan_calendar.ics';
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ setTimeout(() => URL.revokeObjectURL(url), 100);
+ } catch (error) {
+ console.error(error);
+ alert('Failed to export calendar');
+ }
}
function renderTasks() {
@@ -860,11 +831,9 @@ function renderTasks() {
if (subjects.length === 0) return; // Wait for subjects to load
- // Filter based on archived status
const activeTasks = tasks.filter(t => !t.archived);
const archivedTasks = tasks.filter(t => t.archived);
- // Update badges
const allTasksBadge = document.querySelector('#all-tasks-btn .badge');
if (allTasksBadge) {
allTasksBadge.textContent = activeTasks.length;
@@ -874,14 +843,11 @@ function renderTasks() {
archivedBadge.textContent = archivedTasks.length;
}
-
-
const displayTasksRaw = currentView === 'archived' ? archivedTasks : activeTasks;
const displayTasks = activeLabelFilter
? displayTasksRaw.filter(t => t.labels && t.labels.includes(activeLabelFilter))
: displayTasksRaw;
- // Extract unique labels to populate the filter dropdown
if (labelFilterSelect) {
const uniqueLabels = new Set();
store.tasks.forEach(t => {
@@ -890,7 +856,6 @@ function renderTasks() {
}
});
- // Store current selection to restore it
const currentSel = labelFilterSelect.value;
let optionsHtml = '
@@ -1100,18 +1042,11 @@ function renderTasks() {
: '';
const totalMinutes = [...dueSoon, ...completed].reduce((acc, t) => acc + (Number(t.estimated_duration) || 0), 0);
- console.log('[Daily Study Time] Calendar view with selected date:', selectedDate);
- console.log('[Daily Study Time] Pending tasks:', dueSoon.length, 'Completed tasks:', completed.length);
- console.log('[Daily Study Time] Task durations:', [...dueSoon, ...completed].map(t => ({ title: t.title, estimated_duration: t.estimated_duration })));
- console.log('[Daily Study Time] Total minutes calculated:', totalMinutes);
const studyTimeEl = document.getElementById('daily-study-time');
const studyTimeValueEl = document.getElementById('daily-study-time-value');
if (studyTimeEl && studyTimeValueEl) {
studyTimeEl.style.display = 'flex';
studyTimeValueEl.textContent = formatDuration(totalMinutes);
- console.log('[Daily Study Time] Banner shown with value:', formatDuration(totalMinutes));
- } else {
- console.log('[Daily Study Time] Banner elements not found');
}
tasksSection.innerHTML = actionBar +
@@ -1121,54 +1056,32 @@ function renderTasks() {
} else {
const selectedCount = getSelectedTasks().length;
const bulkToolbar = `
-
- ${selectedCount} selected
-
-
- Select All
-
-
-
- Complete
-
-
-
- Archive
-
-
-
- Delete
-
-
-
- Clear
-
-
- `;
+
+ ${selectedCount} selected
+ Select All
+ Complete
+ Archive
+ Delete
+ Clear
+
+ `;
+
+ const actionBar = currentView === 'archived'
+ ? ''
+ : `
+ ${bulkToolbar}
+
+
+ Mark all pending completed (${pending.length})
+
+
+ `;
-const actionBar = currentView === 'archived'
- ? ''
- : `
- ${bulkToolbar}
-
-
-
- Mark all pending completed (${pending.length})
-
-
- `;
- console.log('[Daily Study Time] Hiding banner - currentView:', currentView, 'selectedDate:', selectedDate);
const studyTimeEl = document.getElementById('daily-study-time');
if (studyTimeEl) {
studyTimeEl.style.display = 'none';
}
-
-
const titlePrefix = currentView === 'archived' ? 'Archived: ' : '';
const emptyStateTitle = currentView === 'archived' ? 'No archived tasks' : 'Start your journey';
const emptyStateText = currentView === 'archived'
@@ -1189,34 +1102,34 @@ const actionBar = currentView === 'archived'
`
: '';
- tasksSection.innerHTML =
- actionBar +
- renderGroup(titlePrefix + '⚠ Due soon', dueSoon, 'var(--color-text-danger)', true) +
- renderGroup(titlePrefix + 'This week', thisWeek, 'var(--color-text-secondary)', true) +
- renderGroup(titlePrefix + 'Completed', completed, 'var(--color-text-tertiary)') +
- emptyState;
-
-document.querySelectorAll('.task-item').forEach(taskEl => {
- const selectTask = () => {
- const taskId = taskEl.dataset.id;
- const task = store.tasks.find(t => String(t.id) === String(taskId));
- if (!taskId || (task && task._isEditing)) return;
- toggleTaskSelection(taskId);
- };
+ tasksSection.innerHTML = actionBar +
+ renderGroup(titlePrefix + '⚠ Due soon', dueSoon, 'var(--color-text-danger)', true) +
+ renderGroup(titlePrefix + 'This week', thisWeek, 'var(--color-text-secondary)', true) +
+ renderGroup(titlePrefix + 'Completed', completed, 'var(--color-text-tertiary)') +
+ emptyState;
- taskEl.addEventListener('click', (e) => {
- if (e.target.closest('button, input, .task-actions, .edit-field')) return;
- selectTask();
- });
+ document.querySelectorAll('.task-item').forEach(taskEl => {
+ const selectTask = () => {
+ const taskId = taskEl.dataset.id;
+ const task = store.tasks.find(t => String(t.id) === String(taskId));
+ if (!taskId || (task && task._isEditing)) return;
+ toggleTaskSelection(taskId);
+ };
+
+ taskEl.addEventListener('click', (e) => {
+ if (e.target.closest('button, input, .task-actions, .edit-field')) return;
+ selectTask();
+ });
- taskEl.addEventListener('keydown', (e) => {
- if (e.key !== 'Enter' && e.key !== ' ') return;
- if (e.target.closest('button, input, .edit-field')) return;
- e.preventDefault();
- selectTask();
- });
-});
+ taskEl.addEventListener('keydown', (e) => {
+ if (e.key !== 'Enter' && e.key !== ' ') return;
+ if (e.target.closest('button, input, .edit-field')) return;
+ e.preventDefault();
+ selectTask();
+ });
+ });
}
+
// Bind CTA button in empty state
const emptyStateAddBtn = document.getElementById('empty-state-add-btn');
if (emptyStateAddBtn) {
@@ -1224,7 +1137,7 @@ document.querySelectorAll('.task-item').forEach(taskEl => {
document.getElementById('add-task-btn')?.click();
});
}
-
+
document.querySelectorAll('.edit-task-btn').forEach(el => {
el.addEventListener('click', (e) => {
e.stopPropagation();
@@ -1256,7 +1169,6 @@ document.querySelectorAll('.task-item').forEach(taskEl => {
e.stopPropagation();
const taskId = el.dataset.id;
const itemEl = el.closest('.task-item');
-
const rawTitle = itemEl.querySelector('.board-edit-title').value;
const subject_id = itemEl.querySelector('.board-edit-subject').value;
@@ -1306,13 +1218,8 @@ document.querySelectorAll('.task-item').forEach(taskEl => {
});
});
- document.querySelectorAll('.delete-task-btn').forEach(el => {
- el.addEventListener('click', (e) => {
- e.stopPropagation();
- store.deleteTask(el.dataset.id);
- });
- });
-
+
+
const markAllPendingBtn = document.getElementById('mark-all-pending-btn');
if (markAllPendingBtn) {
markAllPendingBtn.addEventListener('click', (e) => {
@@ -1328,53 +1235,8 @@ document.querySelectorAll('.task-item').forEach(taskEl => {
store.markPendingTasksForDateCompleted(selectedDate);
});
}
- const bulkCompleteBtn =
- document.getElementById('bulk-complete-btn');
-
-if (bulkCompleteBtn) {
- bulkCompleteBtn.addEventListener('click', () => {
- store.bulkCompleteTasks();
- });
-}
-
-const bulkArchiveBtn =
- document.getElementById('bulk-archive-btn');
-
-if (bulkArchiveBtn) {
- bulkArchiveBtn.addEventListener('click', () => {
- store.bulkArchiveTasks();
- });
-}
-
-const bulkDeleteBtn =
- document.getElementById('bulk-delete-btn');
-
-if (bulkDeleteBtn) {
- bulkDeleteBtn.addEventListener('click', () => {
- store.bulkDeleteTasks();
- });
-}
-
-const clearSelectionBtn =
- document.getElementById('clear-selection-btn');
-
-if (clearSelectionBtn) {
- clearSelectionBtn.addEventListener('click', () => {
- store.clearSelectedTasks();
- });
-}
-
-const selectAllBtn =
- document.getElementById('select-all-btn');
-
-if (selectAllBtn) {
- selectAllBtn.addEventListener('click', () => {
- store.selectAllTasks();
- });
-}
}
-
const summaryBox = document.getElementById('summary-box');
if (summaryBox) {
summaryBox.innerHTML = generateSummary(store.tasks, store.subjects);
@@ -1387,7 +1249,6 @@ function renderCalendar() {
const year = currentMonthDate.getFullYear();
const month = currentMonthDate.getMonth();
-
const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
calTitle.textContent = `${monthNames[month]} ${year}`;
@@ -1397,7 +1258,6 @@ function renderCalendar() {
const firstDay = new Date(year, month, 1).getDay();
const daysInMonth = new Date(year, month + 1, 0).getDate();
const prevMonthDays = new Date(year, month, 0).getDate();
-
const today = new Date();
let html = `
${i}
${indicatorHtml}
@@ -1441,13 +1299,11 @@ function renderCalendar() {
const totalCells = firstDay + daysInMonth;
const nextDays = (7 - (totalCells % 7)) % 7;
for (let i = 1; i <= nextDays; i++) {
- html += `
${i}
`;
+ html += `
${i}
`;
}
calGrid.innerHTML = html;
- // Bind day clicks
document.querySelectorAll('.interactive-day').forEach(el => {
el.addEventListener('click', (e) => {
const d = parseInt(e.currentTarget.getAttribute('data-day'));
@@ -1478,9 +1334,7 @@ function renderExtraction() {
let html = ``;
pasteItems.forEach((item, index) => {
- // try to match subject name
const sub = store.subjects.find(s => s.name.toLowerCase().includes((item.subject_name || '').toLowerCase())) || store.subjects[3];
- // Attach subject id to item so Add will work
item.subject_id = sub.id;
if (item._isEditing) {
@@ -1496,16 +1350,12 @@ function renderExtraction() {
${subjectOptions}
-
Task Name
-
Deadline
-
Notes
-
Save Changes
@@ -1548,7 +1398,6 @@ function renderExtraction() {
const title = card.querySelector('.edit-title-input').value;
let dateVal = card.querySelector('.edit-date-input').value;
const notes = card.querySelector('.edit-notes-input').value;
-
const newSubject = store.subjects.find(s => s.id === subjectId);
store.updateExtractedItem(idx, {
@@ -1565,8 +1414,8 @@ function renderExtraction() {
function calculateStreak(tasks) {
const completedTasks = tasks.filter(t => t.status === 'Done' && t.due_at && !t.archived);
-
const dates = new Set();
+
completedTasks.forEach(t => {
const d = new Date(t.due_at);
if (!isNaN(d.getTime())) {
@@ -1578,41 +1427,36 @@ function calculateStreak(tasks) {
const today = new Date();
const todayStr = `${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`;
-
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const yesterdayStr = `${yesterday.getFullYear()}-${yesterday.getMonth()}-${yesterday.getDate()}`;
let streak = 0;
let checkDate = new Date();
- let checkStr = todayStr;
if (dates.has(todayStr)) {
streak = 1;
} else if (dates.has(yesterdayStr)) {
streak = 1;
checkDate = yesterday;
- checkStr = yesterdayStr;
} else {
return 0;
}
while (true) {
checkDate.setDate(checkDate.getDate() - 1);
- checkStr = `${checkDate.getFullYear()}-${checkDate.getMonth()}-${checkDate.getDate()}`;
+ const checkStr = `${checkDate.getFullYear()}-${checkDate.getMonth()}-${checkDate.getDate()}`;
if (dates.has(checkStr)) {
streak++;
} else {
break;
}
}
-
return streak;
}
function renderStreak() {
const streakCount = calculateStreak(store.tasks);
-
const streakCountEl = document.getElementById('streak-count');
if (streakCountEl) {
streakCountEl.textContent = streakCount;
@@ -1628,15 +1472,10 @@ function renderStreak() {
const tooltip = document.getElementById('streak-tooltip');
if (tooltip) {
- if (streakCount >= 30) {
- tooltip.textContent = '30 day badge unlocked';
- } else if (streakCount >= 7) {
- tooltip.textContent = '7 day badge unlocked';
- } else if (streakCount >= 3) {
- tooltip.textContent = '3 day badge unlocked';
- } else {
- tooltip.textContent = 'Complete tasks to build streak & earn cool badges';
- }
+ if (streakCount >= 30) tooltip.textContent = '30 day badge unlocked';
+ else if (streakCount >= 7) tooltip.textContent = '7 day badge unlocked';
+ else if (streakCount >= 3) tooltip.textContent = '3 day badge unlocked';
+ else tooltip.textContent = 'Complete tasks to build streak & earn cool badges';
}
}
@@ -1649,6 +1488,59 @@ store.subscribe(renderSidebarSubjects);
store.subscribe(renderStreak);
document.addEventListener('DOMContentLoaded', () => {
+
+const globalTasksSection = document.getElementById('tasks-section');
+
+if (globalTasksSection) {
+ globalTasksSection.addEventListener('click', async (e) => {
+ // Find if the clicked element or its parent is a delete button
+ const deleteBtn = e.target.closest('.delete-task-btn');
+ if (!deleteBtn) return;
+
+ // Stop propagation and prevent default behavior
+ e.preventDefault();
+ e.stopPropagation();
+
+ const taskId = deleteBtn.dataset.id;
+ if (!taskId) return;
+
+ // store.deleteTask() already asks for confirmation and shows
+ // a success toast internally, so we don't need to repeat it here.
+ store.deleteTask(taskId);
+ });
+}
+
+ const bulkCompleteBtn = document.getElementById('bulk-complete-btn');
+ if (bulkCompleteBtn) {
+ bulkCompleteBtn.addEventListener('click', () => store.bulkCompleteTasks());
+ }
+
+ const bulkArchiveBtn = document.getElementById('bulk-archive-btn');
+ if (bulkArchiveBtn) {
+ bulkArchiveBtn.addEventListener('click', () => store.bulkArchiveTasks());
+ }
+
+ const bulkDeleteBtn = document.getElementById('bulk-delete-btn');
+ if (bulkDeleteBtn) {
+ bulkDeleteBtn.addEventListener('click', async () => {
+ const confirmed = await Toast.confirm("Are you sure you want to permanently delete all selected tasks?");
+ if (confirmed) {
+ store.bulkDeleteTasks();
+ Toast.show("Selected tasks deleted successfully", "success");
+ }
+ });
+ }
+
+ const clearSelectionBtn = document.getElementById('clear-selection-btn');
+ if (clearSelectionBtn) {
+ clearSelectionBtn.addEventListener('click', () => store.clearSelectedTasks());
+ }
+
+ const selectAllBtn = document.getElementById('select-all-btn');
+ if (selectAllBtn) {
+ selectAllBtn.addEventListener('click', () => store.selectAllTasks());
+ }
+
if (newSubjectColorsEl) {
SUBJECT_COLORS.forEach(c => {
const btn = document.createElement('button');
@@ -1810,18 +1702,12 @@ document.addEventListener('DOMContentLoaded', () => {
renderTasks();
});
-//NEw Task addition event listeners
-newTaskBtn.addEventListener('click', () => {
-
- if (!store.subjects || store.subjects.length === 0) {
- Toast.show('Subjects are still loading. Please try again in a moment.', 'warning');
- return;
- }
-
- newTaskSubject.innerHTML = store.subjects
- .map(s => `
${s.name} `)
- .join('');
-
+ newTaskBtn.addEventListener('click', () => {
+ if (!store.subjects || store.subjects.length === 0) {
+ Toast.show('Subjects are still loading. Please try again in a moment.', 'warning');
+ return;
+ }
+ newTaskSubject.innerHTML = store.subjects.map(s => `
${s.name} `).join('');
if (selectedDate) {
const d = new Date(selectedDate);
@@ -1830,10 +1716,10 @@ newTaskBtn.addEventListener('click', () => {
} else {
newTaskDate.value = '';
}
- newTaskTitle.value = '';
- newTaskNotes.value = '';
- if (newTaskEstimatedDuration) newTaskEstimatedDuration.value = '';
- setNewTaskDurationUnit('minutes');
+ newTaskTitle.value = '';
+ newTaskNotes.value = '';
+ if (newTaskEstimatedDuration) newTaskEstimatedDuration.value = '';
+ setNewTaskDurationUnit('minutes');
newTaskModal.style.display = 'flex';
});
@@ -1841,83 +1727,77 @@ newTaskBtn.addEventListener('click', () => {
newTaskModal.style.display = 'none';
});
-newTaskModal.addEventListener('click', (e) => {
- if (e.target === newTaskModal) {
- newTaskModal.style.display = 'none';
- }
+ newTaskModal.addEventListener('click', (e) => {
+ if (e.target === newTaskModal) newTaskModal.style.display = 'none';
});
-
-function setNewTaskDurationUnit(unit) {
- selectedTaskDurationUnit = unit;
- newTaskDurationSwitch?.setAttribute('data-unit', unit);
- newTaskDurationMin?.classList.toggle('active', unit === 'minutes');
- newTaskDurationHr?.classList.toggle('active', unit === 'hours');
-}
-
-newTaskDurationMin?.addEventListener('click', () => setNewTaskDurationUnit('minutes'));
-newTaskDurationHr?.addEventListener('click', () => setNewTaskDurationUnit('hours'));
-
-newTaskSave.addEventListener('click', async () => {
- const rawTitle = newTaskTitle.value.trim();
- const subject_id = newTaskSubject.value;
- const notes = newTaskNotes.value.trim();
- const dateVal = newTaskDate.value;
- const durationValue = newTaskEstimatedDuration ? Number(newTaskEstimatedDuration.value) : 0;
- const estimated_duration = durationValue > 0
- ? Math.round(selectedTaskDurationUnit === 'hours' ? durationValue * 60 : durationValue)
- : null;
-
- if (!rawTitle) {
- alert('Please enter a task name');
- return;
+
+ function setNewTaskDurationUnit(unit) {
+ selectedTaskDurationUnit = unit;
+ newTaskDurationSwitch?.setAttribute('data-unit', unit);
+ newTaskDurationMin?.classList.toggle('active', unit === 'minutes');
+ newTaskDurationHr?.classList.toggle('active', unit === 'hours');
}
- if (!dateVal) {
- alert('Please enter a deadline');
- return;
-}
-
-if (!subject_id) {
- alert('Please select a subject');
- return;
-}
- const { cleanTitle, labels } = extractLabels(rawTitle);
- const due_at = dateVal ? new Date(dateVal).toISOString() : '';
-
- const newTask = {
- title: cleanTitle || rawTitle,
- subject_id,
- due_at,
- notes,
- priority: 'medium',
- status: 'Not Started',
- archived: 0,
- estimated_duration,
- is_estimated_duration_min: selectedTaskDurationUnit === 'minutes' ? 1 : 0,
- labels
- };
+ newTaskDurationMin?.addEventListener('click', () => setNewTaskDurationUnit('minutes'));
+ newTaskDurationHr?.addEventListener('click', () => setNewTaskDurationUnit('hours'));
+
+ newTaskSave.addEventListener('click', async () => {
+ const rawTitle = newTaskTitle.value.trim();
+ const subject_id = newTaskSubject.value;
+ const notes = newTaskNotes.value.trim();
+ const dateVal = newTaskDate.value;
+ const durationValue = newTaskEstimatedDuration ? Number(newTaskEstimatedDuration.value) : 0;
+ const estimated_duration = durationValue > 0
+ ? Math.round(selectedTaskDurationUnit === 'hours' ? durationValue * 60 : durationValue)
+ : null;
+
+ if (!rawTitle) {
+ alert('Please enter a task name');
+ return;
+ }
+ if (!dateVal) {
+ alert('Please enter a deadline');
+ return;
+ }
+ if (!subject_id) {
+ alert('Please select a subject');
+ return;
+ }
+ const { cleanTitle, labels } = extractLabels(rawTitle);
+ const due_at = dateVal ? new Date(dateVal).toISOString() : '';
+
+ const newTask = {
+ title: cleanTitle || rawTitle,
+ subject_id,
+ due_at,
+ notes,
+ priority: 'medium',
+ status: 'Not Started',
+ archived: 0,
+ estimated_duration,
+ is_estimated_duration_min: selectedTaskDurationUnit === 'minutes' ? 1 : 0,
+ labels
+ };
await store.addTasks([newTask]);
newTaskModal.style.display = 'none';
});
-
-addItemsBtn.addEventListener('click', () => {
- if (store.currentPaste) {
- const pasteWithLabels = store.currentPaste.map(t => {
- const { cleanTitle, labels } = extractLabels(t.title);
- return { ...t, title: cleanTitle || t.title, labels };
- });
- store.addTasks(pasteWithLabels);
- store.clearExtracted();
- pasteInput.value = '';
- }
-});
+ addItemsBtn.addEventListener('click', () => {
+ if (store.currentPaste) {
+ const pasteWithLabels = store.currentPaste.map(t => {
+ const { cleanTitle, labels } = extractLabels(t.title);
+ return { ...t, title: cleanTitle || t.title, labels };
+ });
+ store.addTasks(pasteWithLabels);
+ store.clearExtracted();
+ pasteInput.value = '';
+ }
+ });
});
-// Ensures the button is hidden on initial page load if the textarea is empty
if (pasteInput.value.trim() === "") {
- clearBtn.style.display = 'none';
+ clearBtn.style.display = 'none';
}
extractBtn.addEventListener('click', async () => {
@@ -1928,28 +1808,24 @@ extractBtn.addEventListener('click', async () => {
extractBtn.disabled = true;
const items = await extractTasksFromText(text);
-
extractBtn.innerHTML = 'Extract with AI →';
extractBtn.disabled = false;
-
store.setExtracted(items);
});
-// Wipes the text, clears the store, hides the button, and refocuses the cursor
clearBtn.addEventListener('click', () => {
- pasteInput.value = '';
- store.clearExtracted();
- clearBtn.style.display = 'none'; // Hides the clear button instantly
- pasteInput.focus(); // Puts the typing cursor back in the box
+ pasteInput.value = '';
+ store.clearExtracted();
+ clearBtn.style.display = 'none';
+ pasteInput.focus();
});
-// Listens to typing/pasting to show or hide the button dynamically
pasteInput.addEventListener('input', () => {
- if (pasteInput.value.trim().length > 0) {
- clearBtn.style.display = 'block';
- } else {
- clearBtn.style.display = 'none';
- }
+ if (pasteInput.value.trim().length > 0) {
+ clearBtn.style.display = 'block';
+ } else {
+ clearBtn.style.display = 'none';
+ }
});
downloadBtn.addEventListener('click', () => {
@@ -1959,7 +1835,6 @@ downloadBtn.addEventListener('click', () => {
const fileInput = document.getElementById('file-input');
const dropZone = document.getElementById('drop-zone');
-// Handle File Selection via File Explorer
if (fileInput) {
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
@@ -1967,9 +1842,7 @@ if (fileInput) {
});
}
-// Handle Drag & Drop Events
if (dropZone) {
- // Prevent browser from opening the file
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, (e) => {
e.preventDefault();
@@ -1977,24 +1850,20 @@ if (dropZone) {
});
});
- // Add highlight effect
['dragenter', 'dragover'].forEach(eventName => {
dropZone.addEventListener(eventName, () => {
dropZone.classList.add('paste-zone--dragover');
});
});
- // Remove highlight effect
['dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, () => {
dropZone.classList.remove('paste-zone--dragover');
});
});
- // Handle dropped file
dropZone.addEventListener('drop', (e) => {
const dt = e.dataTransfer;
-
if (dt && dt.files.length > 0) {
const file = dt.files[0];
handleFileContent(file);
@@ -2002,39 +1871,29 @@ if (dropZone) {
});
}
-// File Reader Function
function handleFileContent(file) {
const allowedExtensions = ['txt', 'md', 'json'];
const fileExtension = file.name.split('.').pop().toLowerCase();
- // Validate extension
if (!allowedExtensions.includes(fileExtension)) {
alert('Invalid file format. Please upload a .txt, .md, or .json file.');
return;
}
const reader = new FileReader();
-
reader.onload = (e) => {
const pasteInput = document.getElementById('paste-input');
-
if (pasteInput) {
pasteInput.value = e.target.result;
-
- alert(
- `Loaded "${file.name}" successfully! Click "Extract with AI" to find your tasks.`
- );
+ alert(`Loaded "${file.name}" successfully! Click "Extract with AI" to find your tasks.`);
}
};
-
reader.onerror = () => {
alert('Error reading file content. Please try again.');
};
-
reader.readAsText(file);
}
-
const quotes = [
"Small Progress is still Progress",
"Focus on being productive instead of busy",
@@ -2049,27 +1908,24 @@ const quotes = [
];
const quoteEl = document.getElementById('motivational-quotes');
-
if (quoteEl) {
const today = new Date();
const seed = today.toDateString();
-
let hash = 0;
-
for (let i = 0; i < seed.length; i++) {
hash = seed.charCodeAt(i) + ((hash << 5) - hash);
}
-
const index = Math.abs(hash % quotes.length);
-
quoteEl.textContent = quotes[index];
}
-calendarDownloadBtn.addEventListener('click', () => {
- downloadCalendar();
-});
-// ================= AUTH FRONTEND =================
+if (calendarDownloadBtn) {
+ calendarDownloadBtn.addEventListener('click', () => {
+ downloadCalendar();
+ });
+}
+// ================= AUTH FRONTEND =================
const authModal = document.getElementById('auth-modal');
const authTitle = document.getElementById('auth-title');
const authSubtitle = document.getElementById('auth-subtitle');
@@ -2077,31 +1933,22 @@ const authSubmitBtn = document.getElementById('auth-submit-btn');
const authToggleBtn = document.getElementById('auth-toggle-btn');
const authToggleText = document.getElementById('auth-toggle-text');
const authError = document.getElementById('auth-error');
-
const emailInput = document.getElementById('auth-email');
const passwordInput = document.getElementById('auth-password');
-
const logoutBtn = document.getElementById('logout-btn');
let isLoginMode = true;
-// ================= CHECK LOGIN =================
-
const savedUser = localStorage.getItem('studyplan_user');
-
if (savedUser) {
authModal.style.display = 'none';
} else {
authModal.style.display = 'flex';
}
-// ================= TOGGLE LOGIN/SIGNUP =================
-
authToggleBtn.addEventListener('click', (e) => {
e.preventDefault();
-
isLoginMode = !isLoginMode;
-
authError.style.display = 'none';
if (isLoginMode) {
@@ -2119,10 +1966,7 @@ authToggleBtn.addEventListener('click', (e) => {
}
});
-// ================= LOGIN / SIGNUP =================
-
authSubmitBtn.addEventListener('click', async () => {
-
const email = emailInput.value.trim();
const password = passwordInput.value.trim();
@@ -2132,67 +1976,37 @@ authSubmitBtn.addEventListener('click', async () => {
return;
}
- const endpoint = isLoginMode
- ? '/api/auth/login'
- : '/api/auth/signup';
-
+ const endpoint = isLoginMode ? '/api/auth/login' : '/api/auth/signup';
try {
-
const response = await fetch(endpoint, {
method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- email,
- password
- })
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ email, password })
});
const data = await response.json();
-
if (!response.ok) {
authError.textContent = data.error || 'Authentication failed';
authError.style.display = 'block';
return;
}
- // Save logged-in user
localStorage.setItem('studyplan_user', email);
-
authModal.style.display = 'none';
-
location.reload();
-
} catch (err) {
-
authError.textContent = 'Server error';
authError.style.display = 'block';
}
});
-// ================= LOGOUT =================
-
logoutBtn.addEventListener('click', async () => {
-
try {
-
- await fetch('/api/auth/logout', {
- method: 'POST'
- });
-
+ await fetch('/api/auth/logout', { method: 'POST' });
localStorage.removeItem('studyplan_user');
-
location.reload();
-
} catch (err) {
console.error(err);
}
});
-
-
-if (calendarDownloadBtn) {
- calendarDownloadBtn.addEventListener('click', () => {
- downloadCalendar();
- });
-}
+}
\ No newline at end of file
diff --git a/js/store.js b/js/store.js
index 075bc62b..a0010f60 100644
--- a/js/store.js
+++ b/js/store.js
@@ -17,7 +17,9 @@ export const store = {
},
subscribe(listener) {
+ if(!this.listeners.includes(listener)){
this.listeners.push(listener);
+ }
},
notify() {
@@ -269,6 +271,7 @@ export const store = {
await fetch(`/api/tasks/${taskId}`, {
method: 'DELETE'
});
+ Toast.show("Task deleted successfully", "success"); // moved here from app.js
} catch (e) {
this.tasks.splice(taskIndex, 0, removedTask);
this.notify();
diff --git a/package-lock.json b/package-lock.json
index 56528823..e9685a4e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,7 +16,7 @@
"sqlite3": "^6.0.1"
},
"engines": {
- "node": "20.x"
+ "node": ">=20.x"
}
},
"node_modules/@google/genai": {