diff --git a/app.js b/app.js index 7430e97..387f843 100644 --- a/app.js +++ b/app.js @@ -1,40 +1,66 @@ -require("dotenv").config(); -const express = require("express"); -const cors = require("cors"); -const helmet = require("helmet"); -const morgan = require("morgan"); -const rateLimit = require("express-rate-limit"); -const http = require("http"); +require('dotenv').config(); +const express = require('express'); +const cors = require('cors'); +const helmet = require('helmet'); +const morgan = require('morgan'); +const rateLimit = require('express-rate-limit'); +const http = require('http'); +const path = require('path'); const app = express(); const port = process.env.PORT || 3000; -// Middleware for parsing JSON and urlencoded data + app.use(express.json()); app.use(express.urlencoded({ extended: true })); - -app.use(helmet()); // Security Headers -app.use(morgan("short")); -// Rate Limit for brute force attacks +app.use(cors()); +app.use(helmet()); +app.use(morgan('short')); app.use( rateLimit({ - windowMs: 60 * 60 * 1000, // 1 hour + windowMs: 60 * 60 * 1000, max: 100, }), ); + +app.use(express.static(path.join(__dirname))); + +app.post('/api/discovery-contact', (req, res) => { + const { name, email, goal, timeline, message } = req.body; + + if (!name || !email || !goal || !timeline || !message) { + return res.status(400).json({ error: 'All discovery contact fields are required.' }); + } + + return res.status(201).json({ message: 'Discovery contact submitted successfully.' }); +}); + +app.post('/api/subscribe', (req, res) => { + const { email } = req.body; + + if (!email) { + return res.status(400).json({ error: 'Email is required.' }); + } + + return res.status(201).json({ message: 'Subscription successful.' }); +}); + +app.get('*', (req, res) => { + res.sendFile(path.join(__dirname, 'index.html')); +}); + const server = http.createServer(app); -process.on("uncaughtException", (error) => { - console.error("Uncaught exception:", error); +process.on('uncaughtException', (error) => { + console.error('Uncaught exception:', error); server.close(() => process.exit(1)); }); -server.listen(port, () => { - console.log("Server is started on port " + port); +process.on('unhandledRejection', (error) => { + console.error('Unhandled rejection:', error); }); -process.on("unhandledRejection", (error) => { - console.error("Unhandled Rejection:", error); -}); -process.on("rejectionHandled", (error) => { - console.error("Rejection handled:", error); + +server.listen(port, () => { + console.log(`Server is started on port ${port}`); }); + module.exports = app; diff --git a/index.html b/index.html index ade3fd3..238c5ec 100644 --- a/index.html +++ b/index.html @@ -1,240 +1,88 @@
- - -Plan your upcoming sessions and keep your progress on track.
${task.description}
Due: ${new Date(task.dueDate).toDateString()}
`; - taskList.appendChild(div); + const taskCard = document.createElement('div'); + taskCard.innerHTML = `${escapeHtml(task.description)}
Due: ${new Date(task.dueDate).toDateString()}
`; + taskList.appendChild(taskCard); +} + +function initDiscoveryContactForm() { + const form = document.getElementById('discoveryContactForm'); + const statusElement = document.getElementById('discoveryContactStatus'); + + if (!form || !statusElement) { + return; + } + + form.addEventListener('submit', async (event) => { + event.preventDefault(); + + const formData = new FormData(form); + const payload = Object.fromEntries(formData.entries()); + + if (!payload.name || !payload.email || !payload.goal || !payload.timeline || !payload.message) { + setStatus(statusElement, 'Please complete every field before submitting.', true); + return; + } + + try { + const response = await fetch('/api/discovery-contact', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }); + + if (!response.ok) { + throw new Error('Contact endpoint failed.'); + } + + setStatus(statusElement, 'Thanks! Your discovery request has been submitted.'); + form.reset(); + } catch (error) { + console.error('Discovery contact submission failed:', error); + setStatus(statusElement, 'We could not submit your request. Please try again.', true); + } + }); +} + +function initFooterSubscribeForm() { + const form = document.getElementById('footerSubscribeForm'); + const statusElement = document.getElementById('subscribeStatus'); + + if (!form || !statusElement) { + return; + } + + form.addEventListener('submit', async (event) => { + event.preventDefault(); + + const formData = new FormData(form); + const email = String(formData.get('email') || '').trim(); + + if (!email) { + setStatus(statusElement, 'Please enter an email address.', true); + return; + } + + try { + const response = await fetch('/api/subscribe', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email }), + }); + + if (!response.ok) { + throw new Error('Subscribe endpoint failed.'); + } + + setStatus(statusElement, 'Subscribed! Check your inbox for updates.'); + form.reset(); + } catch (error) { + console.error('Footer subscribe submission failed:', error); + setStatus(statusElement, 'Subscription failed. Please try again.', true); + } + }); +} + +function setStatus(element, message, isError = false) { + element.textContent = message; + element.classList.remove('success', 'error'); + element.classList.add(isError ? 'error' : 'success'); +} + +function escapeHtml(value) { + return String(value) + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll("'", '''); } diff --git a/style.css b/style.css index 45e1133..1099651 100644 --- a/style.css +++ b/style.css @@ -1,65 +1,135 @@ - -/* CSS reset for consistency across browsers */ -* { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -/* Root font size for easy scaling with rem units */ -html { - font-size: 16px; -} - -/* Typography for body content */ -body { - font-family: 'Arial', sans-serif; - background-color: #f4f4f4; - line-height: 1.6; - color: #333; -} - -/* Header styling */ -header { - background-color: #007bff; - color: white; - padding: 1rem; - text-align: center; -} - -header h1 { - margin: 0; -} - -/* Main content area and sections */ -main { - padding: 1rem; -} - -section { - background-color: white; - margin-bottom: 1rem; - padding: 1rem; - border-radius: 0.5rem; -} - -/* Headings within sections */ -h2 { - margin-bottom: 0.5rem; -} - -/* Footer styling */ -footer { - background-color: #f8f8f8; - text-align: center; - padding: 1rem; -} - -/* Responsive design for larger screens */ -@media (min-width: 768px) { - section { - margin: 1rem auto; - max-width: 80%; - } -} - +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + font-size: 16px; +} + +body { + font-family: Arial, sans-serif; + background-color: #f4f4f4; + line-height: 1.6; + color: #333; +} + +header { + background-color: #007bff; + color: #fff; + padding: 1rem; + text-align: center; +} + +main { + padding: 1rem; +} + +section { + background-color: #fff; + margin-bottom: 1rem; + padding: 1rem; + border-radius: 0.5rem; +} + +h2 { + margin-bottom: 0.75rem; +} + +form { + display: grid; + gap: 0.5rem; +} + +label { + font-weight: 600; +} + +input, +select, +textarea, +button { + font: inherit; +} + +input, +select, +textarea { + width: 100%; + border: 1px solid #c5c5c5; + border-radius: 0.375rem; + padding: 0.55rem 0.65rem; + background: #fff; + color: #1f2937; +} + +/* Force readable dropdown text and options across browsers */ +select, +select option { + color: #111827; + background-color: #ffffff; +} + +button { + border: 0; + border-radius: 0.375rem; + background: #007bff; + color: #fff; + cursor: pointer; + padding: 0.6rem 0.9rem; +} + +button:hover { + background: #0065d1; +} + +#taskList { + margin-top: 1rem; + display: grid; + gap: 0.75rem; +} + +#taskList > div { + border: 1px solid #e5e7eb; + border-radius: 0.375rem; + padding: 0.75rem; +} + +footer { + background-color: #f8f8f8; + text-align: center; + padding: 1rem; +} + +.subscribe-form { + margin: 0.75rem auto 0; + max-width: 600px; + text-align: left; +} + +.subscribe-row { + display: grid; + grid-template-columns: 1fr auto; + gap: 0.5rem; +} + +.form-status { + min-height: 1.25rem; + font-size: 0.92rem; +} + +.form-status.success { + color: #166534; +} + +.form-status.error { + color: #991b1b; +} + +@media (min-width: 768px) { + section { + margin: 1rem auto; + max-width: 80%; + } +}