Merge pull request #37 from JoaquinPar/refactor/plans

refactor/ new plan as a modal pop up. Deletion and edition on plan cr…
This commit is contained in:
Nícolas Agostini authored and GitHub committed 2025-05-20 14:27:20 -07:00
commit 273009f91f
7 files changed
+414 -73

No files matched your search

+39
View File
@@ -0,0 +1,39 @@
function editPlan(planId) {
if (planId) {
window.location.href = `/plans/${planId}/edit`;
} else {
console.error('editPlan called without a planId');
alert('Cannot edit plan: Plan ID is missing.');
}
}
async function deletePlan(planId) {
if (!planId) {
console.error('deletePlan called without a planId');
alert('Cannot delete plan: Plan ID is missing.');
return;
}
if (confirm('Are you sure you want to delete this plan? This action cannot be undone.')) {
try {
const response = await fetch(`/plans/${planId}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
});
const result = await response.json();
if (response.ok && result.success) {
alert('Plan deleted successfully.');
window.location.href = '/plans'; // Redirect to the plans list page
} else {
alert(`Failed to delete plan: ${result.message || 'Unknown error'}`);
}
} catch (error) {
console.error('Error deleting plan:', error);
alert('An error occurred while trying to delete the plan. Please check the console for details and ensure the server is running.');
}
}
}
+121 -24
View File
@@ -1,5 +1,5 @@
const getRates = require("../util/exchangeRate");
const { calculateProgress, updatePlanProgressInDB } = require("../util/calculations");
const { calculateProgress, updatePlanProgressInDB, updateProgress } = require("../util/calculations");
const suggestions = require("../util/suggestions");
const status = require("../util/statuses");
const ObjectId = require('mongodb').ObjectId;
@@ -118,16 +118,9 @@ module.exports = (middleware, users, plans, assets) => {
router.get('/plans', async (req, res) => {
try {
const userPlansFromDB = await plans.find({ userId: new ObjectId(req.session.userId) }).toArray();
for (const plan of userPlansFromDB) {
const progress = await calculateProgress(plan, assets, users, req.session.user._id);
await updatePlanProgressInDB(plan._id, progress.percentage, plans);
}
const updatedUserPlans = await plans.find({ userId: new ObjectId(req.session.user._id) }).toArray();
const updatedUserPlans = await updateProgress(plans, assets, users, req.session.user._id);
res.render('plans', {
user: req.session.user,
plans: updatedUserPlans,
@@ -150,23 +143,21 @@ module.exports = (middleware, users, plans, assets) => {
req.session.errMessage = "Invalid plan ID format.";
return res.status(status.BadRequest).redirect('/plans');
}
const plan = await plans.findOne({ userId: new ObjectId(req.session.userId), _id: new ObjectId(planId) });
if (!plan) {
console.log(`Plan not found with ID: ${planId} for user: ${req.session.userId}`);
req.session.errMessage = "Plan not found or you do not have permission to view it.";
return res.status(status.NotFound).redirect('/plans');
}
const progress = await calculateProgress(plan, assets, users, req.session.userId);
const progress = await calculateProgress(plan, assets, users, req.session.userId);
res.render('planDetail', {
user: req.session.user,
plan: plan,
plan: plan,
geoData: req.session.geoData,
assets: userAssets,
progress: progress,
progress: progress,
suggestions: await suggestions.generateSuggestions(),
});
@@ -205,29 +196,134 @@ module.exports = (middleware, users, plans, assets) => {
if (error) {
console.error("Plan validation error:", error.details);
req.session.errMessage = "Invalid input: " + error.details.map(d => d.message.replace(/"/g, '')).join(', ');
res.status(status.BadRequest).redirect("/newPlan");
const errorMessage = "Invalid input: " + error.details.map(d => d.message.replace(/"/g, '')).join(', ');
res.status(status.BadRequest).json({ success: false, message: errorMessage });
return;
}
const newPlan = {
const planToInsert = {
userId: new ObjectId(req.session.userId),
name: value.name,
retirementAge: value.retirementAge,
retirementExpenses: value.retirementExpenses,
retirementAssets: value.retirementAssets,
retirementLiabilities: value.retirementLiabilities,
progress: "0"
progress: "0"
};
try {
await plans.insertOne({ userId: new ObjectId(req.session.userId), ...newPlan });
req.session.errMessage = "";
res.redirect('/plans');
await plans.insertOne(planToInsert);
res.status(status.Ok).json({ success: true, message: "Plan created successfully." });
}
catch (err) {
console.error("Error saving plan:", err);
req.session.errMessage = "An error occurred while saving your plan. Please try again.";
res.status(status.InternalServerError).redirect("/newPlan");
res.status(status.InternalServerError).json({ success: false, message: "An error occurred while saving your plan. Please try again." });
}
});
router.delete('/plans/:id', async (req, res) => {
const planId = req.params.id;
const plan = await plans.findOne({ _id: new ObjectId(planId) });
if(!plan) {
console.log(`Plan not found with ID: ${planId}`);
res.status(status.NotFound).json({ success: false, message: "Plan not found or you do not have permission to delete it." });
return;
}
if(plan.userId.toString() !== req.session.userId) {
console.log(`User ${req.session.userId} does not have permission to delete plan ${planId}`);
res.status(status.Forbidden).json({ success: false, message: "You do not have permission to delete this plan." });
return;
}
try {
await plans.deleteOne({ _id: new ObjectId(planId) });
res.status(status.Ok).json({ success: true, message: "Plan deleted successfully." });
}
catch (err) {
console.error("Error deleting plan:", err);
res.status(status.InternalServerError).json({ success: false, message: "An error occurred while deleting your plan. Please try again." });
}
});
router.get('/plans/:id/edit', async (req, res) => {
const planId = req.params.id;
const plan = await plans.findOne({ _id: new ObjectId(planId) });
if(!plan) {
console.log(`Plan not found with ID: ${planId}`);
res.status(status.NotFound).json({ success: false, message: "Plan not found or you do not have permission to edit it." });
return;
}
if(plan.userId.toString() !== req.session.userId) {
console.log(`User ${req.session.userId} does not have permission to edit plan ${planId}`);
res.status(status.Forbidden).json({ success: false, message: "You do not have permission to edit this plan." });
return;
}
res.render('editPlan', {
user: req.session.user,
plan: plan,
geoData: req.session.geoData
});
});
router.post('/plans/:id/edit', async (req, res) => {
const planId = req.params.id;
const plan = await plans.findOne({ _id: new ObjectId(planId) });
if(!plan) {
console.log(`Plan not found with ID: ${planId}`);
res.status(status.NotFound).json({ success: false, message: "Plan not found or you do not have permission to edit it." });
return;
}
if(plan.userId.toString() !== req.session.userId) {
console.log(`User ${req.session.userId} does not have permission to edit plan ${planId}`);
res.status(status.Forbidden).json({ success: false, message: "You do not have permission to edit this plan." });
return;
}
const planSchema = joi.object({
name: joi.string().min(3).max(100).required(),
retirementAge: joi.number().min(18).max(120).required(),
retirementExpenses: joi.number().min(0).required(),
retirementAssets: joi.number().min(0).required(),
retirementLiabilities: joi.number().min(0).required(),
});
// Object for Joi validation - only fields from req.body
const dataToValidate = {
name: req.body.name,
retirementAge: Number(req.body.retirementAge),
retirementExpenses: parseFloat(req.body.retirementExpenses),
retirementAssets: parseFloat(req.body.retirementAssets),
retirementLiabilities: parseFloat(req.body.retirementLiabilities),
};
const { error, value } = planSchema.validate(dataToValidate);
if (error) {
console.error("Plan validation error:", error.details);
const errorMessage = "Invalid input: " + error.details.map(d => d.message.replace(/"/g, '')).join(', ');
res.status(status.BadRequest).json({ success: false, message: errorMessage });
return;
}
const progressCalculated = await calculateProgress(value, assets, users, req.session.userId);
const planToSet = {
name: value.name,
retirementAge: value.retirementAge,
retirementExpenses: value.retirementExpenses,
retirementAssets: value.retirementAssets,
retirementLiabilities: value.retirementLiabilities,
progress: progressCalculated.percentage,
};
try {
await plans.updateOne(
{ _id: new ObjectId(planId), userId: new ObjectId(req.session.userId) },
{ $set: planToSet }
);
await updateProgress(plans, assets, users, req.session.user._id);
res.status(status.Ok).json({ success: true, message: "Plan updated successfully." });
}
catch (err) {
console.error("Error updating plan:", err);
res.status(status.InternalServerError).json({ success: false, message: "An error occurred while updating your plan. Please try again." });
}
});
@@ -415,6 +511,7 @@ module.exports = (middleware, users, plans, assets) => {
return res.status(status.Ok).redirect("/profile");
});
}).catch(err => {
console.error("Error updating account in database:", err);
req.session.errMessage = "An error occurred while saving your information. Please try again.";
+21 -6
View File
@@ -67,15 +67,17 @@ async function calculateProgress(plan, assets, users, userId) {
const totalUserPlanValue = plan.retirementAssets;
const userDoc = await users.findOne({ _id: new ObjectId(userId) });
if (!userDoc || !userDoc.dob) {
console.error("User document or DOB not found for userId:", userId);
return { monthlyInvestment: NaN, totalCostOfRetirement: NaN, monthsUntilRetirement: NaN, yearsRetired: NaN, percentage: NaN };
console.error("[calculateProgress] User document or DOB not found for userId:", userId);
// Ensure a structured return even on error to avoid undefined.progress issues
return { monthlyInvestment: NaN, totalCostOfRetirement: NaN, monthsUntilRetirement: NaN, yearsRetired: NaN, yearsUntilRetirement: NaN, percentage: NaN };
}
const userDob = new Date(userDoc.dob);
if (isNaN(userDob.getTime())) {
console.error("userDob is an invalid date. Aborting calculation.");
return { monthlyInvestment: NaN, totalCostOfRetirement: NaN, monthsUntilRetirement: NaN, yearsRetired: NaN, percentage: NaN };
console.error("[calculateProgress] userDob is an invalid date. Aborting calculation.");
return { monthlyInvestment: NaN, totalCostOfRetirement: NaN, monthsUntilRetirement: NaN, yearsRetired: NaN, yearsUntilRetirement: NaN, percentage: NaN };
}
const today = new Date();
@@ -104,10 +106,23 @@ async function calculateProgress(plan, assets, users, userId) {
} catch (err) {
console.error("Error in calculateProgress:", err);
return;
// Ensure a structured return even on error to avoid undefined.progress issues
return { monthlyInvestment: NaN, totalCostOfRetirement: NaN, monthsUntilRetirement: NaN, yearsRetired: NaN, yearsUntilRetirement: NaN, percentage: NaN };
}
}
async function updateProgress(plans, assets, users, userId) {
const userPlansFromDB = await plans.find({ userId: new ObjectId(userId) }).toArray();
for (const plan of userPlansFromDB) {
const progress = await calculateProgress(plan, assets, users, userId);
await updatePlanProgressInDB(plan._id, progress.percentage, plans);
}
const updatedUserPlans = await plans.find({ userId: new ObjectId(userId) }).toArray();
return updatedUserPlans;
}
module.exports = { calculatePlanProgress, updatePlanProgressInDB, calculateProgress};
module.exports = { calculatePlanProgress, updatePlanProgressInDB, calculateProgress, updateProgress};
+102
View File
@@ -0,0 +1,102 @@
<%- include('./partials/fileHeader') %>
<%- include('./partials/header') %>
<main class="container mx-auto p-4 pt-28">
<div class="max-w-2xl mx-auto bg-gray-800 p-6 sm:p-8 rounded-xl shadow-xl">
<h2 class="text-2xl font-bold text-center text-white mb-8">Edit Plan: <%= plan.name %></h2>
<form id="editPlanForm" action="/plans/<%= plan._id %>/edit" method="POST" class="space-y-6">
<div>
<label for="name" class="block text-sm font-medium text-gray-300">Plan Name</label>
<input type="text" name="name" id="name" value="<%= plan.name %>" required
class="mt-1 block w-full px-4 py-2 border border-gray-600 rounded-md shadow-sm bg-gray-700 text-white focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm placeholder-gray-400">
</div>
<div>
<label for="retirementAge" class="block text-sm font-medium text-gray-300">Desired Retirement Age</label>
<input type="number" name="retirementAge" id="retirementAge" value="<%= plan.retirementAge %>" required min="18"
class="mt-1 block w-full px-4 py-2 border border-gray-600 rounded-md shadow-sm bg-gray-700 text-white focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm placeholder-gray-400">
</div>
<div>
<label for="retirementExpenses" class="block text-sm font-medium text-gray-300">Estimated Monthly Expenses at Retirement (<%= geoData.currency || 'CAD' %>)</label>
<input type="number" name="retirementExpenses" id="retirementExpenses" value="<%= plan.retirementExpenses %>" required min="0" step="any"
class="mt-1 block w-full px-4 py-2 border border-gray-600 rounded-md shadow-sm bg-gray-700 text-white focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm placeholder-gray-400">
</div>
<div>
<label for="retirementAssets" class="block text-sm font-medium text-gray-300">Target Retirement Assets (<%= geoData.currency || 'CAD' %>)</label>
<input type="number" name="retirementAssets" id="retirementAssets" value="<%= plan.retirementAssets %>" required min="0" step="any"
class="mt-1 block w-full px-4 py-2 border border-gray-600 rounded-md shadow-sm bg-gray-700 text-white focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm placeholder-gray-400">
</div>
<div>
<label for="retirementLiabilities" class="block text-sm font-medium text-gray-300">Estimated Monthly Liabilities at Retirement (<%= geoData.currency || 'CAD' %>)</label>
<input type="number" name="retirementLiabilities" id="retirementLiabilities" value="<%= plan.retirementLiabilities %>" required min="0" step="any"
class="mt-1 block w-full px-4 py-2 border border-gray-600 rounded-md shadow-sm bg-gray-700 text-white focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm placeholder-gray-400">
</div>
<div id="formErrorMessages" class="text-red-400 text-sm my-2" style="display: none;"></div>
<div class="flex items-center justify-end space-x-4 pt-4">
<a href="/plans/<%= plan._id %>" class="inline-flex justify-center py-2 px-4 border border-gray-600 rounded-md shadow-sm bg-gray-600 text-sm font-medium text-white hover:bg-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-indigo-500">
Cancel
</a>
<button type="submit"
class="inline-flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-indigo-500">
Save Changes
</button>
</div>
</form>
</div>
</main>
<script>
// Basic client-side form submission handler to process JSON response
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('editPlanForm');
const errorMessagesDiv = document.getElementById('formErrorMessages');
if (form) {
form.addEventListener('submit', async function(event) {
event.preventDefault(); // Prevent traditional form submission
errorMessagesDiv.textContent = '';
errorMessagesDiv.style.display = 'none';
const formData = new FormData(form);
const data = {};
formData.forEach((value, key) => {
data[key] = value;
});
try {
const response = await fetch(form.action, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
const result = await response.json();
if (response.ok && result.success) {
// alert('Plan updated successfully!'); // Optional: show an alert
window.location.href = `/plans/<%= plan._id %>`; // Redirect to plan detail page
} else {
errorMessagesDiv.textContent = result.message || 'An error occurred while updating the plan.';
errorMessagesDiv.style.display = 'block';
}
} catch (error) {
console.error('Error submitting form:', error);
errorMessagesDiv.textContent = 'A network error occurred. Please try again.';
errorMessagesDiv.style.display = 'block';
}
});
}
});
</script>
<%- include('./partials/navBar') %>
<%- include('./partials/scriptLoader') %>
<%- include('./partials/footer') %>
-41
View File
@@ -1,41 +0,0 @@
<%- include("./partials/fileHeader") %>
<%- include("./partials/header") %>
<main class="container mx-auto p-4 pt-30">
<div class="max-w-md mx-auto bg-white p-8 mb-10 rounded-lg shadow-md">
<h3 class="text-lg font-medium mb-6">Retirement Plan</h3>
<form action="/newPlan" method="post" class="space-y-4">
<div>
<label for="name" class="block text-sm font-medium text-gray-700">Plan Name</label>
<input type="text" name="name" placeholder="e.g., Retirement Plan" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div>
<label for="retirementAge" class="block text-sm font-medium text-gray-700">Desired Retirement Age</label>
<input type="number" name="retirementAge" placeholder="e.g., 65" min="18" max="120" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div>
<label for="retirementExpenses" class="block text-sm font-medium text-gray-700">Estimated Monthly Retirement Expenses</label>
<input type="number" name="retirementExpenses" min="0" placeholder="e.g., 3000" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div>
<label for="retirementAssets" class="block text-sm font-medium text-gray-700">Estimated Retirement Assets</label>
<input type="number" name="retirementAssets" min="0" placeholder="e.g., 500000" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div>
<label for="retirementLiabilities" class="block text-sm font-medium text-gray-700">Estimated Retirement Liabilities</label>
<input type="number" name="retirementLiabilities" min="0" placeholder="e.g., 50000" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
</div>
<div>
<button type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">Save Plan</button>
</div>
</form>
</div>
</main>
<%- include("./partials/navBar") %>
<%- include("./partials/scriptLoader") %>
<%- include("./partials/footer") %>
+6
View File
@@ -87,10 +87,16 @@
</p>
</div>
</div>
<div class="flex items-center justify-center space-x-2">
<button type="button" class="py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 cursor-pointer" onclick="editPlan('<%= plan._id %>')">Edit Plan</button>
<button type="button" class="py-2 px-4 border border-red-600 rounded-md shadow-sm text-sm font-medium text-white bg-red-600 hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 cursor-pointer" onclick="deletePlan('<%= plan._id %>')">Delete Plan</button>
</div>
</div>
</main>
<script src="/static/scripts/planManager.js"></script>
<%- include("./partials/navBar") %>
<%- include("./partials/scriptLoader") %>
<%- include("./partials/footer") %>
+125 -2
View File
@@ -4,7 +4,8 @@
<main class="container mx-auto p-4 pt-28">
<div class="max-w-md mx-auto bg-white p-8 mb-10 rounded-lg shadow-md space-y-4">
<h3 class="text-lg font-medium mb-6">My Retirement Plans</h3>
<a href="/newPlan" class="block max-w-sm p-2"><div class="text-center bg-blue-600 text-white py-1 w-full rounded-md hover:bg-blue-700 hover:text-white font-semibold">New Plan</div></a>
<!-- Changed link to a button to open modal -->
<button id="openNewPlanModalBtn" class="block w-full max-w-sm p-2"><div class="text-center bg-blue-600 text-white py-1 w-full rounded-md hover:bg-blue-700 hover:text-white font-semibold">New Plan</div></button>
<hr class="mt-3">
<% plans.forEach(plan => { %>
<a href="/plans/<%= plan._id %>" class="block max-w-sm p-6 border rounded-lg shadow-sm bg-gray-800 border-gray-700 hover:bg-gray-700">
@@ -16,9 +17,131 @@
</a>
<% }) %>
</div>
<!-- New Plan Modal - Styled like createAsset.ejs -->
<dialog id="newPlanDialog" class="w-lg mx-auto bg-white p-8 mt-10 rounded-lg shadow-md">
<!-- Header: Title and Cancel button -->
<div class="flex flex-row justify-between items-center mb-4">
<h3 class="text-lg font-bold text-gray-900">Create New Retirement Plan</h3>
<form method="dialog" id="cancelNewPlanDialogForm">
<button type="submit" class="py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 cursor-pointer">Cancel</button>
</form>
</div>
<!-- Form Content -->
<div>
<form id="newPlanForm" class="space-y-4">
<div>
<label for="name" class="block text-sm font-medium text-gray-700 text-left">Plan Name</label>
<input type="text" name="name" id="planName" placeholder="e.g., My Awesome Plan" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" required>
</div>
<div>
<label for="retirementAge" class="block text-sm font-medium text-gray-700 text-left">Desired Retirement Age</label>
<input type="number" name="retirementAge" id="retirementAge" placeholder="e.g., 65" min="18" max="120" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" required>
</div>
<div>
<label for="retirementExpenses" class="block text-sm font-medium text-gray-700 text-left">Estimated Monthly Retirement Expenses</label>
<input type="number" name="retirementExpenses" id="retirementExpenses" min="0" placeholder="e.g., 3000" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" required>
</div>
<div>
<label for="retirementAssets" class="block text-sm font-medium text-gray-700 text-left">Estimated Retirement Assets</label>
<input type="number" name="retirementAssets" id="retirementAssets" min="0" placeholder="e.g., 500000" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" required>
</div>
<div>
<label for="retirementLiabilities" class="block text-sm font-medium text-gray-700 text-left">Estimated Retirement Liabilities</label>
<input type="number" name="retirementLiabilities" id="retirementLiabilities" min="0" placeholder="e.g., 50000" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" required>
</div>
<div id="newPlanError" class="text-red-500 text-sm mt-2" style="display: none;"></div>
<!-- Form Buttons: Save Plan and Reset -->
<div class="mt-6 flex flex-row justify-between">
<button type="button" id="resetNewPlanFormBtn" class="py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">Reset</button>
<button id="submitNewPlanBtn" type="submit" class="py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">Save Plan</button>
</div>
</form>
</div>
</dialog>
</main>
<%- include("./partials/navBar") %>
<%- include("./partials/scriptLoader") %>
<script>
document.addEventListener('DOMContentLoaded', () => {
const openModalBtn = document.getElementById('openNewPlanModalBtn');
const newPlanDialog = document.getElementById('newPlanDialog');
const newPlanForm = document.getElementById('newPlanForm');
const submitNewPlanBtn = document.getElementById('submitNewPlanBtn');
const newPlanError = document.getElementById('newPlanError');
const resetNewPlanFormBtn = document.getElementById('resetNewPlanFormBtn');
openModalBtn.addEventListener('click', () => {
newPlanDialog.showModal();
// Error reset is now handled by 'close' event, but good to clear on open too
newPlanError.style.display = 'none';
newPlanError.textContent = '';
});
// Handles form reset and error clearing when dialog is closed by any means (Esc, Cancel button, backdrop click, successful submit)
newPlanDialog.addEventListener('close', () => {
newPlanForm.reset();
newPlanError.style.display = 'none';
newPlanError.textContent = '';
});
// Close dialog if user clicks on the backdrop
newPlanDialog.addEventListener('click', (event) => {
if (event.target === newPlanDialog) {
newPlanDialog.close(); // This will trigger the 'close' event listener above
}
});
resetNewPlanFormBtn.addEventListener('click', () => {
newPlanForm.reset();
newPlanError.style.display = 'none';
newPlanError.textContent = '';
});
newPlanForm.addEventListener('submit', async (event) => {
event.preventDefault();
submitNewPlanBtn.disabled = true;
submitNewPlanBtn.textContent = 'Saving...';
newPlanError.style.display = 'none';
const formData = new FormData(newPlanForm);
const data = Object.fromEntries(formData.entries());
try {
const response = await fetch('/newPlan', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (response.ok) {
const result = await response.json();
if (result.success) {
newPlanDialog.close(); // This will trigger the 'close' event listener
window.location.reload(); // Reload to see the new plan
} else {
newPlanError.textContent = result.message || 'Failed to create plan. Please try again.';
newPlanError.style.display = 'block';
}
} else {
const errorData = await response.json();
newPlanError.textContent = errorData.message || `Error: ${response.status} - ${response.statusText}`;
newPlanError.style.display = 'block';
}
} catch (error) {
console.error('Error submitting new plan:', error);
newPlanError.textContent = 'An unexpected error occurred. Please try again.';
newPlanError.style.display = 'block';
}
finally {
submitNewPlanBtn.disabled = false;
submitNewPlanBtn.textContent = 'Save Plan';
}
});
});
</script>
<%- include("./partials/footer") %>