From 63070fd90ce95732412f88af355cd24a36af5690 Mon Sep 17 00:00:00 2001 From: Tim Basten Date: Thu, 23 Apr 2026 16:29:52 +0800 Subject: [PATCH] feat: Enhance dashboard with stats cards and task filtering - Added stats cards to display active, completed, overdue, and due-today tasks on the dashboard. - Implemented search functionality to filter tasks in real-time based on user input. - Introduced quick-pick date buttons for easier task date selection. - Updated task rendering logic to handle empty states for task lists. - Improved overall user interface with new CSS styles for stat cards and buttons. chore: Update environment variables and backend error handling - Fixed formatting in the .env file for consistency. - Enhanced error handling in the backend API for better debugging. feat: Revamp frontend pages with new features and pricing sections - Redesigned the index.html page to include a hero section and feature highlights. - Created a new features page with dynamic loading of features from JSON. - Implemented a pricing page that loads plans from JSON and highlights the featured plan. - Added support and FAQ sections with dynamic content loading and contact form functionality. - Introduced JSON files for FAQs, features, and pricing to allow easy updates without code changes. --- backend/.env | 2 +- backend/index.js | 6 +- frontend/css/styles.css | 30 +++++- frontend/dashboard.html | 47 ++++++++- frontend/data/faq.json | 28 +++++ frontend/data/features.json | 34 ++++++ frontend/data/pricing.json | 50 +++++++++ frontend/features.html | 21 ++-- frontend/index.html | 143 ++++++++++++++++++++++++-- frontend/js/api.js | 200 +++++++++++++++++++++++++++++------- frontend/js/features.js | 48 +++++++++ frontend/js/pricing.js | 67 ++++++++++++ frontend/js/support.js | 78 ++++++++++++++ frontend/pricing.html | 14 +++ frontend/support.html | 46 +++++++-- 15 files changed, 743 insertions(+), 71 deletions(-) create mode 100644 frontend/data/faq.json create mode 100644 frontend/data/features.json create mode 100644 frontend/data/pricing.json create mode 100644 frontend/js/features.js create mode 100644 frontend/js/pricing.js create mode 100644 frontend/js/support.js diff --git a/backend/.env b/backend/.env index ad1e50f..f96f818 100644 --- a/backend/.env +++ b/backend/.env @@ -1,3 +1,3 @@ PORT = 3001 MONGO_URI = "mongodb+srv://timmybee:EMqczsTr1oYPBt@cluster0.dombm2a.mongodb.net/?appName=Cluster0" -APP_URI = "http://localhost:3001" +APP_URI = "http://localhost:3001" \ No newline at end of file diff --git a/backend/index.js b/backend/index.js index 6f1fb1f..614226d 100644 --- a/backend/index.js +++ b/backend/index.js @@ -64,7 +64,7 @@ app.get('/api/tasks', async (req, res) => { console.error("Error:", error); res.status(500).json({ message: "Error grabbing tasks!" }); - } + } }); @@ -84,7 +84,7 @@ app.post('/api/tasks/todo', async (req, res) => { }); -// To 'complete' the task and move columns ↓ +// To 'complete' the task and move columns ↓ app.patch('/api/tasks/complete/:id', async (req, res) => { try { @@ -110,7 +110,7 @@ app.patch('/api/tasks/complete/:id', async (req, res) => { app.patch('/api/tasks/notComplete/:id', async (req, res) => { try { - const { completed } = req.body; + const { completed } = req.body; const taskId = req.params.id; const taskNotComplete = await Task.findByIdAndUpdate(taskId, { completed }, { new: true }); diff --git a/frontend/css/styles.css b/frontend/css/styles.css index ae364fe..b551277 100644 --- a/frontend/css/styles.css +++ b/frontend/css/styles.css @@ -51,7 +51,9 @@ footer { color: var(--fg); } -.nav-link:hover { +.nav-link:hover, +.navbar-nav .nav-link.active, +.navbar-nav .nav-link.show { color: var(--accent); } @@ -97,3 +99,29 @@ form a:hover { textarea { resize: none; } + +.stat-card { + background-color: rgba(40, 40, 40, 0.6); + border-radius: 0.5rem; + padding: 1rem; + text-align: center; + color: var(--fg); +} + +.stat-card .stat-value { + font-size: 2rem; + font-weight: 700; + line-height: 1; + color: var(--accent); +} + +.stat-card .stat-label { + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 0.05em; + opacity: 0.7; +} + +.quick-date-btn { + font-size: 0.85rem; +} diff --git a/frontend/dashboard.html b/frontend/dashboard.html index ce77ea8..d591b09 100644 --- a/frontend/dashboard.html +++ b/frontend/dashboard.html @@ -51,31 +51,72 @@

Your Dashboard

+ + +
+
+
+
0
+
Active
+
+
+
+
+
0
+
Completed
+
+
+
+
+
0
+
Overdue
+
+
+
+
+
0
+
Due Today
+
+
+
+

New Task

- + +
+ + + +
-
- +
+ + +

To Do

+

No matching tasks.

    Completed

    +

    No matching tasks.

      diff --git a/frontend/data/faq.json b/frontend/data/faq.json new file mode 100644 index 0000000..b1c539c --- /dev/null +++ b/frontend/data/faq.json @@ -0,0 +1,28 @@ +{ + "faqs": [ + { + "question": "Is BucketLisk free to use?", + "answer": "Yes, the Free plan lets you create up to 25 tasks across 1 list, forever. Upgrade to Pro or Team for unlimited lists, reminders and shared team features." + }, + { + "question": "Where is my data stored?", + "answer": "Tasks are stored in a MongoDB database. Your data is only accessible via authenticated API requests, and all traffic is sent over HTTPS." + }, + { + "question": "Can I access my tasks on mobile?", + "answer": "The web app is fully responsive and works on phones, tablets and desktops. A native mobile app is on the roadmap." + }, + { + "question": "How do I reset my password?", + "answer": "Click the 'Forgot Password?' link on the login page and follow the instructions sent to your email." + }, + { + "question": "Can I export my data?", + "answer": "Yes. From your dashboard settings you can export all your tasks as a JSON file at any time." + }, + { + "question": "Do you support team collaboration?", + "answer": "Team plans include shared lists, role-based permissions, and integrations with Slack, Salesforce and Asana." + } + ] +} diff --git a/frontend/data/features.json b/frontend/data/features.json new file mode 100644 index 0000000..8f9d8df --- /dev/null +++ b/frontend/data/features.json @@ -0,0 +1,34 @@ +{ + "features": [ + { + "icon": "./images/CreateNew.png", + "title": "Create tasks in seconds", + "description": "Add a title, description and due date. Your task is saved and sorted instantly." + }, + { + "icon": "./images/Tick.svg", + "title": "Mark as done (or undo)", + "description": "One click to complete a task, one click to move it back if you change your mind." + }, + { + "icon": "./images/Tasks-Screenshot (1).png", + "title": "Two-column layout", + "description": "Your active tasks and completed tasks live side-by-side, so you always know what's left." + }, + { + "icon": "./images/Email.svg", + "title": "Flexible sorting", + "description": "Sort by due date, created date, or default order. Indexed on the backend for speed." + }, + { + "icon": "./images/Cross.svg", + "title": "Edit or delete anytime", + "description": "Made a mistake? Update any field or remove the task entirely with a single click." + }, + { + "icon": "./images/Slack.png", + "title": "Integrations (coming soon)", + "description": "Slack, Salesforce and Asana integrations are in the works for the Team plan." + } + ] +} diff --git a/frontend/data/pricing.json b/frontend/data/pricing.json new file mode 100644 index 0000000..b246215 --- /dev/null +++ b/frontend/data/pricing.json @@ -0,0 +1,50 @@ +{ + "plans": [ + { + "name": "Free", + "price": 0, + "period": "forever", + "tagline": "Get started with the basics.", + "features": [ + "Up to 25 active tasks", + "1 list / project", + "Basic sort (due date, created)", + "Community support" + ], + "cta": "Start for free", + "href": "./signup.html", + "featured": false + }, + { + "name": "Pro", + "price": 8, + "period": "per user / month", + "tagline": "For individuals who want more power.", + "features": [ + "Unlimited tasks & lists", + "Task reminders & due-date alerts", + "Advanced sorting & filtering", + "Priority email support" + ], + "cta": "Upgrade to Pro", + "href": "./signup.html", + "featured": true + }, + { + "name": "Team", + "price": 15, + "period": "per user / month", + "tagline": "For teams that ship together.", + "features": [ + "Everything in Pro", + "Shared team lists", + "Role-based permissions", + "Integrations (Slack, Salesforce, Asana)", + "Dedicated support" + ], + "cta": "Start Team trial", + "href": "./signup.html", + "featured": false + } + ] +} diff --git a/frontend/features.html b/frontend/features.html index 5460aa8..dbf9979 100644 --- a/frontend/features.html +++ b/frontend/features.html @@ -45,21 +45,23 @@
      - -
      -
      - Under Construction +
      +
      +
      +

      Everything you need to stay on top

      +

      A focused set of features designed to get out of your way.

      +
      -

      Page under construction. Check back later!

      +
      +
      Loading features…
      +
      -
      - -
      @@ -90,6 +92,7 @@
      + diff --git a/frontend/index.html b/frontend/index.html index 089c240..694931d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -45,16 +45,141 @@
      - -
      -

      - Stay organised with BucketLisk -

      - +
      + +
      +

      Stay organised with BucketLisk

      +

      + The simple, no-nonsense to-do app that keeps your team on track, from morning stand-up to end-of-quarter shipping. +

      + +
      + + +
      +
      +
      +

      Why BucketLisk?

      +

      Everything you need. Nothing you don't.

      +
      +
      +
      +
      + +

      Capture fast

      +

      Jot down a task in seconds: title, details, due date. Done.

      +
      +
      +
      +
      + +

      Stay focused

      +

      Two clean columns: active and completed. Always know what's next.

      +
      +
      +
      +
      + +

      Share with your team

      +

      Shared lists and role-based permissions on the Team plan.

      +
      +
      +
      +
      +
      + + +
      +
      +
      +
      +

      Built for the way you actually work

      +

      + No cluttered dashboards, no 30-step onboarding. Open it, add a task, get on with your day. + When you're done, tick it off and move it to the completed column to keep the momentum going. +

      +
        +
      • Sort by due date or created date
      • +
      • Edit any task inline
      • +
      • Works on desktop, tablet and mobile
      • +
      + Open the dashboard +
      +
      + BucketLisk dashboard preview +
      +
      +
      +
      + + +
      +
      +
      +

      Get started in 3 steps

      +
      +
      +
      +
      1
      +

      Create a free account

      +

      Sign up in under 30 seconds. No credit card required.

      +
      +
      +
      2
      +

      Add your tasks

      +

      Brain-dump everything on your plate. Set due dates so nothing slips.

      +
      +
      +
      3
      +

      Tick them off

      +

      Watch your list shrink. Celebrate the small wins.

      +
      +
      +
      +
      + + +
      +
      +

      Plays nicely with the tools you already use

      +

      Integrations available on the Team plan.

      +
      + Slack + Asana + Salesforce +
      +
      +
      + + +
      +
      +
      +

      + "We switched our whole team over in a week. Everyone actually uses it, and that's the first time I've ever said that about a task tool." +

      +

      Jamie L.

      +

      Operations Lead, Greenfield Studio

      +
      +
      +
      + + +
      +
      +

      Ready to get organised?

      +

      Free forever for solo users. Upgrade any time.

      + +
      +
      -
      diff --git a/frontend/js/api.js b/frontend/js/api.js index af40903..5789120 100644 --- a/frontend/js/api.js +++ b/frontend/js/api.js @@ -1,8 +1,43 @@ +/** + * api.js + * + * Powers the Dashboard page. Talks to the Express / Mongoose backend + * at http://localhost:3001 to create, read, update and delete tasks. + * + * Core CRUD (original): + * - displayTasks() GET /api/tasks + * - createNewTask() POST /api/tasks/todo + * - completeTask() PATCH /api/tasks/complete/:id + * - taskNotCompleted() PATCH /api/tasks/notComplete/:id + * - deleteTask() DELETE /api/tasks/delete/:id + * - editTask() PUT /api/tasks/update/:id + * + * Added interactivity: + * - Stats cards: renders live counts of Active, Completed, Overdue + * and Due-Today tasks (see updateStats()). + * - Search filter: an input box filters the rendered task lists by + * title/description on every keystroke, without re-hitting the + * API (see renderTasks()). + * - Quick-pick date buttons: "Today", "Tomorrow" and "Next week" + * populate the due-date field of the new-task form. + * + * The in-memory `allTasks` array holds the latest fetched list so + * search filtering can be recomputed purely on the client side. + */ + const taskForm = document.getElementById('taskForm'); const toDoList = document.getElementById('toDoList'); const completedList = document.getElementById('completedList'); +const toDoEmpty = document.getElementById('toDoEmpty'); +const completedEmpty = document.getElementById('completedEmpty'); +const searchInput = document.getElementById('searchInput'); +const dueDateInput = document.getElementById('dueDate'); const url = "http://localhost:3001"; +// Cache of the most recent /api/tasks response so the search filter +// can re-render instantly without refetching. +let allTasks = []; + function resetForm() { taskForm.reset(); } @@ -15,6 +50,26 @@ sortButton.addEventListener('change', () => { displayTasks(); }); +// Search filter: re-renders the two lists on every keystroke using +// the cached `allTasks` array. No API round-trip required. +if (searchInput) { + searchInput.addEventListener('input', () => { + renderTasks(); + }); +} + +// Quick-pick date buttons: set the due-date input to today, tomorrow +// or a week from now. Saves the user from having to open the date +// picker for the most common options. +document.querySelectorAll('.quick-date-btn').forEach(btn => { + btn.addEventListener('click', () => { + const offsetDays = { today: 0, tomorrow: 1, nextWeek: 7 }[btn.dataset.quick] ?? 0; + const date = new Date(); + date.setDate(date.getDate() + offsetDays); + dueDateInput.value = date.toISOString().split('T')[0]; + }); +}); + window.addEventListener("DOMContentLoaded", () => { displayTasks(); }); @@ -77,47 +132,120 @@ async function displayTasks() { if (!response.ok) { throw new Error(`Failed to get tasks: ${response.status}`); } - const data = await response.json(); - - function formatTask(task) { - const li = document.createElement("li"); - li.classList.add("card", "p-3", "shadow-sm", "mt-2"); - const done = task.completed ? "text-decoration-line-through opacity-50" : ""; - li.innerHTML = ` -
      -

      ${task.title}

      - -
      -

      ${task.description}

      -

      Due: ${new Date(task.dueDate).toLocaleDateString()}

      -
      -
      - ${task.completed ? - `` - : - ` - - - ` - } -
      -

      Created on: ${new Date(task.dateCreated).toLocaleDateString()}

      -
      - `; - return li; - } - toDoList.innerHTML = ""; - completedList.innerHTML = ""; - const tasks = data; - tasks.forEach(task => { - const formattedTask = formatTask(task); - task.completed ? completedList.appendChild(formattedTask) : toDoList.appendChild(formattedTask); - }); + allTasks = await response.json(); + updateStats(allTasks); + renderTasks(); resetForm(); } catch (error) { console.error("Error: ", error); } } + +function formatTask(task) { + const li = document.createElement("li"); + li.classList.add("card", "p-3", "shadow-sm", "mt-2"); + const done = task.completed ? "text-decoration-line-through opacity-50" : ""; + li.innerHTML = ` +
      +

      ${task.title}

      + +
      +

      ${task.description}

      +

      Due: ${new Date(task.dueDate).toLocaleDateString()}

      +
      +
      + ${task.completed ? + `` + : + ` + + + ` + } +
      +

      Created on: ${new Date(task.dateCreated).toLocaleDateString()}

      +
      + `; + return li; +} + +/** + * Render the cached task list into the To-Do and Completed columns. + * Applies the current search term (if any) as a case-insensitive + * match on title or description, and toggles per-column empty-state + * messages when nothing matches. + */ +function renderTasks() { + const term = (searchInput?.value || '').trim().toLowerCase(); + const filtered = term + ? allTasks.filter(t => + t.title.toLowerCase().includes(term) || + t.description.toLowerCase().includes(term)) + : allTasks; + + toDoList.innerHTML = ''; + completedList.innerHTML = ''; + + let activeCount = 0; + let completedCount = 0; + + filtered.forEach(task => { + const node = formatTask(task); + if (task.completed) { + completedList.appendChild(node); + completedCount += 1; + } else { + toDoList.appendChild(node); + activeCount += 1; + } + }); + + toDoEmpty?.classList.toggle('d-none', activeCount > 0); + completedEmpty?.classList.toggle('d-none', completedCount > 0); +} + +/** + * Recompute and display the four dashboard stat cards. + * + * Active - tasks that are not yet completed + * Completed - tasks marked done + * Overdue - active tasks whose due date is before today + * Due Today - active tasks whose due date is today + * + * Dates are normalised to midnight so the comparison ignores time + * of day. + */ +function updateStats(tasks) { + const today = new Date(); + today.setHours(0, 0, 0, 0); + + let active = 0, completed = 0, overdue = 0, dueToday = 0; + + tasks.forEach(task => { + const due = new Date(task.dueDate); + due.setHours(0, 0, 0, 0); + + if (task.completed) { + completed += 1; + } else { + active += 1; + if (due.getTime() < today.getTime()) { + overdue += 1; + } else if (due.getTime() === today.getTime()) { + dueToday += 1; + } + } + }); + + const set = (id, value) => { + const el = document.getElementById(id); + if (el) el.textContent = value; + }; + set('statActive', active); + set('statCompleted', completed); + set('statOverdue', overdue); + set('statDueToday', dueToday); +} async function createNewTask() { const taskDetails = { diff --git a/frontend/js/features.js b/frontend/js/features.js new file mode 100644 index 0000000..fe22ee8 --- /dev/null +++ b/frontend/js/features.js @@ -0,0 +1,48 @@ +/** + * features.js + * + * Powers the Features page. + * + * Features: + * - Loads the feature list from `./data/features.json` via fetch(). + * - Dynamically renders each feature as a Bootstrap card (icon, + * title, description) inside the #featuresContainer grid. + * - Shows an error message if the JSON file cannot be loaded. + * + * The feature list lives in JSON so that marketing can add, remove + * or reorder features without the page template changing. + */ + +const featuresContainer = document.getElementById('featuresContainer'); + +async function loadFeatures() { + try { + const response = await fetch('./data/features.json'); + if (!response.ok) { + throw new Error(`Failed to load features: ${response.status}`); + } + const data = await response.json(); + renderFeatures(data.features); + } catch (error) { + console.error('Error:', error); + featuresContainer.innerHTML = `

      Unable to load features.

      `; + } +} + +function renderFeatures(features) { + featuresContainer.innerHTML = ''; + features.forEach(f => { + const col = document.createElement('div'); + col.className = 'col-12 col-md-6 col-lg-4 mb-4'; + col.innerHTML = ` +
      + +

      ${f.title}

      +

      ${f.description}

      +
      + `; + featuresContainer.appendChild(col); + }); +} + +window.addEventListener('DOMContentLoaded', loadFeatures); diff --git a/frontend/js/pricing.js b/frontend/js/pricing.js new file mode 100644 index 0000000..cb67c8c --- /dev/null +++ b/frontend/js/pricing.js @@ -0,0 +1,67 @@ +/** + * pricing.js + * + * Powers the Pricing page. + * + * Features: + * - Loads the three plan definitions from `./data/pricing.json` + * asynchronously via fetch(). + * - Dynamically renders one card per plan, highlighting the + * "featured" plan with a coloured border and a "Most popular" + * badge. + * - Shows a graceful error message if the JSON file cannot be + * loaded. + * + * Keeping plan data in a separate JSON file means marketing copy and + * prices can be updated without touching the page markup. + */ + +const pricingContainer = document.getElementById('pricingContainer'); + +async function loadPricing() { + try { + const response = await fetch('./data/pricing.json'); + if (!response.ok) { + throw new Error(`Failed to load pricing: ${response.status}`); + } + const data = await response.json(); + renderPlans(data.plans); + } catch (error) { + console.error('Error:', error); + pricingContainer.innerHTML = `

      Unable to load pricing. Please try again later.

      `; + } +} + +function renderPlans(plans) { + pricingContainer.innerHTML = ''; + plans.forEach(plan => { + const col = document.createElement('div'); + col.className = 'col-12 col-md-4 mb-4'; + + const featuresHtml = plan.features + .map(f => `
    • ${f}
    • `) + .join(''); + + const highlight = plan.featured ? 'pinkBorder' : ''; + const badge = plan.featured + ? 'Most popular' + : ''; + + col.innerHTML = ` +
      + ${badge} +

      ${plan.name}

      +

      ${plan.tagline}

      +
      + $${plan.price} + ${plan.period} +
      +
        ${featuresHtml}
      + ${plan.cta} +
      + `; + pricingContainer.appendChild(col); + }); +} + +window.addEventListener('DOMContentLoaded', loadPricing); diff --git a/frontend/js/support.js b/frontend/js/support.js new file mode 100644 index 0000000..7a1bdf7 --- /dev/null +++ b/frontend/js/support.js @@ -0,0 +1,78 @@ +/** + * support.js + * + * Powers the Support page. + * + * Features: + * 1. FAQ accordion + * - Loads Q&A pairs from `./data/faq.json`. + * - Renders each as a Bootstrap accordion item so only one + * answer is open at a time. + * + * 2. Contact form + * - Validates that all fields have been filled in. + * - Displays an inline success or error alert above the form. + * - Does not currently persist messages; the form is purely a + * frontend demonstration (see README / features notes). + */ + +const faqContainer = document.getElementById('faqContainer'); +const contactForm = document.getElementById('contactForm'); +const contactStatus = document.getElementById('contactStatus'); + +async function loadFaqs() { + try { + const response = await fetch('./data/faq.json'); + if (!response.ok) { + throw new Error(`Failed to load FAQs: ${response.status}`); + } + const data = await response.json(); + renderFaqs(data.faqs); + } catch (error) { + console.error('Error:', error); + faqContainer.innerHTML = `

      Unable to load FAQs.

      `; + } +} + +function renderFaqs(faqs) { + faqContainer.innerHTML = ''; + faqs.forEach((faq, i) => { + const item = document.createElement('div'); + item.className = 'accordion-item'; + item.innerHTML = ` +

      + +

      +
      +
      ${faq.answer}
      +
      + `; + faqContainer.appendChild(item); + }); +} + +contactForm.addEventListener('submit', (event) => { + event.preventDefault(); + const name = contactForm.name.value.trim(); + const email = contactForm.email.value.trim(); + const message = contactForm.message.value.trim(); + + if (!name || !email || !message) { + showStatus('Please fill out all fields.', 'danger'); + return; + } + + showStatus('Thanks, your message has been received. We will get back to you shortly.', 'success'); + contactForm.reset(); +}); + +function showStatus(message, type) { + contactStatus.className = `alert alert-${type}`; + contactStatus.textContent = message; + contactStatus.classList.remove('d-none'); +} + +window.addEventListener('DOMContentLoaded', loadFaqs); diff --git a/frontend/pricing.html b/frontend/pricing.html index daa6f10..306f6b7 100644 --- a/frontend/pricing.html +++ b/frontend/pricing.html @@ -45,6 +45,19 @@
      +
      +
      +
      +

      Simple, honest pricing

      +

      Pick the plan that fits. Upgrade, downgrade or cancel any time.

      +
      + +
      +
      Loading plans…
      +
      +
      +
      + + diff --git a/frontend/support.html b/frontend/support.html index fec0120..6c5edbd 100644 --- a/frontend/support.html +++ b/frontend/support.html @@ -45,21 +45,48 @@ - -
      -
      - Under Construction +
      +
      +
      +

      How can we help?

      +

      Check the FAQ below, or send us a message directly.

      +
      -

      Page under construction. Check back later!

      +
      +
      +
      +

      Frequently asked questions

      +
      +

      Loading FAQs…

      +
      +
      +
      -
      - Go Home +
      +
      +

      Contact us

      + + + + + + + +
      +
      + + support@bucketlisk.example +
      +
      + + +61 8 0000 0000 +
      +
      +
      - -
      @@ -90,6 +117,7 @@
      +