Compare commits

..
47 Commits
Author SHA1 Message Date
knighthawk4227 b3b06226ee fix/mobile layout 2025-05-22 12:39:17 -07:00
SowinskiBraeden 19081e3bd3 merge latest dev 2025-05-22 11:13:00 -07:00
SowinskiBraeden 7269814501 fix/limit age 2025-05-22 11:12:36 -07:00
nicoagostini 16f267bcd4 fix/calculations capped if you try to retire today 2025-05-22 11:09:15 -07:00
nicoagostini 8998283552 Merge branch 'dev' of https://github.com/JoaquinPar/2800-202510-BBY14 into dev 2025-05-22 10:43:29 -07:00
nicoagostini 5b6ce3b09d fix/Depreciation fixed, if asset value < 1000, use the input value 2025-05-22 10:42:53 -07:00
SowinskiBraeden e45b7ca946 merge latest dev 2025-05-22 10:38:50 -07:00
SowinskiBraeden c0e2d01ea6 fix/asset name aphanum restriction 2025-05-22 10:38:22 -07:00
nicoagostini 3d55a2b0d0 fix/ vehicle depreciation capped at one thousand 2025-05-22 10:37:05 -07:00
nicoagostini ec309fbafd fix/ Reinforce user to complete the financial questionnaire 2025-05-22 10:33:13 -07:00
knighthawk4227 c1adb31657 fixing indigo button 2025-05-22 00:21:35 -07:00
knighthawk4227 05fa5558e3 open modal of plan after pressing create new plan 2025-05-21 17:02:35 -07:00
knighthawk4227 d2e69f230f fixing conflicts 2025-05-21 16:54:17 -07:00
Nícolas Agostini 0672981e69 Merge pull request #38 from JoaquinPar/feature/vehicle-depreciation
feature/vehicle depreciation calculated towards total assets value
2025-05-21 16:40:15 -07:00
nicoagostini 5b147d4f46 feature/vehicle depreciation calculated towards total assets value 2025-05-21 16:39:19 -07:00
SowinskiBraeden d6425043cf Merge branch 'dev' of github.com:JoaquinPar/2800-202510-BBY14 into dev 2025-05-21 16:38:01 -07:00
SowinskiBraeden cf3d3738f1 fix/default hide year on asset create 2025-05-21 16:37:48 -07:00
knighthawk4227 cc6fd937db buttons blue 2025-05-21 16:16:29 -07:00
SowinskiBraeden b12d4bebe1 update/add year to car assets 2025-05-21 15:40:29 -07:00
SowinskiBraeden 7c09df0924 fix/asset modified date + des whitespace 2025-05-21 15:13:09 -07:00
SowinskiBraeden 44e674c7c9 Merge branch 'dev' of github.com:JoaquinPar/2800-202510-BBY14 into dev 2025-05-21 15:03:45 -07:00
SowinskiBraeden 7c9e92345d fix/assets empty description 2025-05-21 15:03:26 -07:00
nicoagostini 17e3bdf4f4 fix/margin top on assets.ejs changed to match the application 2025-05-21 14:40:08 -07:00
SowinskiBraeden da0bfea655 Merge branch 'dev' of github.com:JoaquinPar/2800-202510-BBY14 into dev 2025-05-21 13:59:20 -07:00
SowinskiBraeden 7fe94e1934 fix/password 2025-05-21 13:58:39 -07:00
nicoagostini 7fd7fa28ff fix/Router after editing fixed to plan/PlanId 2025-05-21 13:56:56 -07:00
SowinskiBraeden 163cb73b9d fix/validation error messages 2025-05-21 12:06:57 -07:00
SowinskiBraeden 9a61d362be update/project structure 2025-05-21 11:38:16 -07:00
SowinskiBraeden db392d1878 Merge branch 'dev' of github.com:JoaquinPar/2800-202510-BBY14 into dev 2025-05-21 11:33:43 -07:00
SowinskiBraeden e50047b26a refactor/add headers to client side scripts 2025-05-21 11:33:26 -07:00
Joaquin 5bc26e334e fix/fixed input fields on login/signup not being required 2025-05-21 11:22:36 -07:00
SowinskiBraeden ce9345fad9 refactor/add required indicators 2025-05-21 11:17:12 -07:00
SowinskiBraeden 90d507e33c refactor/format assets 2025-05-21 11:14:47 -07:00
SowinskiBraeden dd9a0a1027 fix/allow decimal values in assets 2025-05-21 11:04:42 -07:00
Joaquin 231aca9ec5 fix/added required tooltips to some field forms 2025-05-21 11:04:08 -07:00
SowinskiBraeden 796dc245b2 fix/password reset email body 2025-05-21 10:53:34 -07:00
SowinskiBraeden 3dc1faaed5 fix/password reset 2025-05-21 10:47:27 -07:00
Joaquin 5b3d5cdce1 fix/improved user info retention on signup/login forms 2025-05-21 09:39:13 -07:00
nicoagostini 07b8325a14 fix/required fields are marked 2025-05-20 20:12:28 -07:00
nicoagostini c0174c2b22 fix/ now assets display the location currency OR CAD as default 2025-05-20 16:45:49 -07:00
nicoagostini 08397a1c89 refactor/edit plan 'reset' button added 2025-05-20 14:56:03 -07:00
Nícolas Agostini 273009f91f Merge pull request #37 from JoaquinPar/refactor/plans
refactor/ new plan as a modal pop up. Deletion and edition on plan cr…
2025-05-20 14:27:20 -07:00
knighthawk4227 436e242cf1 message if no plans 2025-05-20 14:25:48 -07:00
Joaquin dd1ad02b32 Merge branch 'dev' of https://github.com/JoaquinPar/2800-202510-BBY14 into dev 2025-05-20 14:07:09 -07:00
Joaquin 50bd21b164 fix/user info is now kept on form reset 2025-05-20 14:05:34 -07:00
knighthawk4227 ef858bcf00 no plan form/ check 2025-05-20 12:57:17 -07:00
Joaquin de99301738 fix/fixed background of password reset 2025-05-20 11:16:55 -07:00
30 changed files with 813 additions and 360 deletions

No files matched your search

+13 -7
View File
@@ -53,16 +53,22 @@ Example:
``` ```
retirementCalculator/ retirementCalculator/
├── src/ ├── src/
├── views/ | ├── auth/
│ │ ── partials/ | ── database/
│ ├── css/ │ ├── public/
│ ├── images/ ├── images/
│ ├── scripts/ ├── scripts/
└── utils/ │ └── svgs/
| |
│ ├── router/
│ ├── utils/
│ └── views/
│ └── partials/
├── app.js
├── .env.example ├── .env.example
├── .gitignore ├── .gitignore
├── app.js
├── CONTRIBUTING.md
├── LICENSE ├── LICENSE
├── package-lock.json ├── package-lock.json
├── package.json ├── package.json
+22 -2
View File
@@ -54,9 +54,21 @@ app.get('/', (req, res) => {
}); });
app.get('/signup', (req, res) => { app.get('/signup', (req, res) => {
let error = req.session.errMessage;
delete req.session.errMessage;
const ignore = ["User not found", "Incorrect password"]; const ignore = ["User not found", "Incorrect password"];
if (ignore.includes(req.session.errMessage)) req.session.errMessage = ""; if (ignore.includes(error)) error = "";
res.render('signup', { errMessage: req.session.errMessage });
if (req.query.name && req.query.email) {
res.render('signup', {
errMessage: error,
name: req.query.name,
email: req.query.email
});
} else {
res.render('signup', { errMessage: error });
}
return res.status(status.Ok); return res.status(status.Ok);
}); });
@@ -65,7 +77,15 @@ app.get('/login', (req, res) => {
res.redirect("/home"); res.redirect("/home");
return res.status(status.Ok); return res.status(status.Ok);
} }
if (req.query.email) {
res.render('login', {
errMessage: req.session.errMessage,
email: req.query.email
});
} else {
res.render('login', { errMessage: req.session.errMessage }); res.render('login', { errMessage: req.session.errMessage });
}
return res.status(status.Ok); return res.status(status.Ok);
}); });
+14 -13
View File
@@ -27,14 +27,14 @@ module.exports = (users) => {
router.post("/login", async (req, res) => { router.post("/login", async (req, res) => {
const credentialSchema = joi.object({ const credentialSchema = joi.object({
email: joi.string().email().required(), email: joi.string().email({ minDomainSegments: 2, tlds: { allow: true } }).required(),
password: joi.string().alphanum().max(20).required(), password: joi.string().max(20).required(),
}); });
const valid = credentialSchema.validate(req.body); const valid = credentialSchema.validate(req.body);
if (valid.err) { if (valid.error) {
req.session.errMessage = "Invalid input"; req.session.errMessage = "Invalid input:" + valid.error.details.map(d => d.message.replace(/"/g, '')).join(', ');
res.status(status.BadRequest); res.status(status.BadRequest);
return res.redirect("/login"); return res.redirect("/login");
} }
@@ -43,13 +43,13 @@ module.exports = (users) => {
if (!user) { if (!user) {
req.session.errMessage = "User not found"; req.session.errMessage = "User not found";
res.status(status.NotFound); res.status(status.NotFound);
return res.redirect("/login"); return res.redirect(`/login/?email=${req.body.email}`);
} }
if (!bcrypt.compareSync(req.body.password, user.password)) { if (!bcrypt.compareSync(req.body.password, user.password)) {
req.session.errMessage = "Incorrect password"; req.session.errMessage = "Incorrect password";
res.status(status.Unauthorized); res.status(status.Unauthorized);
return res.redirect("/login"); return res.redirect(`/login/?email=${req.body.email}`);
} }
req.session.authenticated = true; req.session.authenticated = true;
@@ -72,16 +72,16 @@ module.exports = (users) => {
router.post("/signup", async (req, res) => { router.post("/signup", async (req, res) => {
const userSchema = joi.object({ const userSchema = joi.object({
email: joi.string().email().required(), email: joi.string().email({ minDomainSegments: 2, tlds: { allow: true } }).required(),
name: joi.string().pattern(new RegExp('^[a-zA-Z]+$')).max(20).required(), name: joi.string().pattern(new RegExp('^[a-zA-Z]+$')).max(20).required(),
password: joi.string().alphanum().max(20).min(8).required(), password: joi.string().max(20).min(8).required(),
repassword: joi.string().alphanum().max(20).min(8).required(), repassword: joi.string().max(20).min(8).required(),
}); });
const valid = userSchema.validate(req.body); const valid = userSchema.validate(req.body);
if (valid.err) { if (valid.error) {
req.session.errMessage = "Invalid input", req.session.errMessage = valid.error.details[0].message,
res.status(status.BadRequest); res.status(status.BadRequest);
return res.redirect("/signup"); return res.redirect("/signup");
} }
@@ -95,8 +95,9 @@ module.exports = (users) => {
if (req.body.password != req.body.repassword) { if (req.body.password != req.body.repassword) {
req.session.errMessage = "Passwords must match"; req.session.errMessage = "Passwords must match";
res.status(status.BadRequest); res.status(status.BadRequest);
return res.redirect("/signup"); return res.redirect(`/signup/?name=${req.body.name}&email=${req.body.email}`);
} }
let strength = passwordStrength(req.body.password); let strength = passwordStrength(req.body.password);
@@ -104,7 +105,7 @@ module.exports = (users) => {
if (strength.id < 2) { if (strength.id < 2) {
req.session.errMessage = `Password ${strength.value}`; req.session.errMessage = `Password ${strength.value}`;
res.status(status.BadRequest); res.status(status.BadRequest);
return res.redirect("/signup"); return res.redirect(`/signup/?name=${req.body.name}&email=${req.body.email}`);
} }
let hashedPassword = await bcrypt.hashSync(req.body.password, salt); let hashedPassword = await bcrypt.hashSync(req.body.password, salt);
+22 -8
View File
@@ -1,8 +1,9 @@
const { passwordStrength } = require("check-password-strength");
const nodeMail = require('nodemailer');
const express = require('express'); const express = require('express');
const bcrypt = require('bcrypt');
const crypto = require('crypto'); const crypto = require('crypto');
const joi = require('joi'); const joi = require('joi');
const nodeMail = require('nodemailer');
const bcrypt = require('bcrypt');
require('dotenv').config(); require('dotenv').config();
const PORT = process.env.PORT; const PORT = process.env.PORT;
@@ -20,7 +21,7 @@ module.exports = (users) => {
router.post('/auth/resetPass', async (req, res) => { router.post('/auth/resetPass', async (req, res) => {
const resetSchema = joi.object({ const resetSchema = joi.object({
email: joi.string().email().required(), email: joi.string().email({ minDomainSegments: 2, tlds: { allow: true } }).required(),
}); });
req.session.error = ''; req.session.error = '';
req.session.reset = ''; req.session.reset = '';
@@ -40,20 +41,26 @@ module.exports = (users) => {
const token = crypto.randomBytes(32).toString('hex'); const token = crypto.randomBytes(32).toString('hex');
console.log(`The reset token is ${token}`) console.log(`The reset token is ${token}`)
const expiration = Date.now() + 360000; const expiration = Date.now() + 3600000;
await users.updateOne({ email }, { await users.updateOne({ email }, {
$set: { resetToken: token, resetTokenExpires: expiration } $set: { resetToken: token, resetTokenExpires: expiration }
}); });
const resetUrl = `http://localhost:${PORT}/reset/${token}`; const resetUrl = `https://rcalculator.sowinski.dev/${PORT}/reset/${token}`;
const mailSend = { const mailSend = {
from: process.env.EMAIL_USER, from: process.env.EMAIL_USER,
to: email, to: email,
subject: 'Password reset', subject: 'Password reset',
text: `reset your password here ${resetUrl} this link will expire within 1 hour`, text: `Hi ${user.name},\nA request was sent to reset your password. If this wasn't you, please ignore this email.\nIf you sent the request, reset your password here ${resetUrl} this link will expire within 1 hour, \n Do not share this link with anyone.
\n \n Thankyou, The RCalculator team.`,
html: `
<p>Hi ${user.name}</p>
<p>A request was sent to reset your password. <strong>If this wasn't you, please ignore this email.</strong></p>
<p>If you sent the request, reset your password <a href="${resetUrl}">here</a>. This link will expire in 1 hour.</p>
<p><img src="https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/png/rekor.png" alt="wallet icon" width="150"/></p>
<p>Thank you,<br/>The RCalculator team</p>`,
}; };
try { try {
@@ -76,7 +83,7 @@ module.exports = (users) => {
const valid = passwordSchema.validate({ password, confirmPassword }); const valid = passwordSchema.validate({ password, confirmPassword });
if (valid.error) { if (valid.error) {
console.log("houston we have a problem"); // nice console.log("houston we have a problem"); // nice
req.session.error = 'Invalid input'; req.session.error = "Invalid input:" + valid.error.details.map(d => d.message.replace(/"/g, '')).join(', ');;
res.status(status.BadRequest); res.status(status.BadRequest);
return res.redirect(`/reset/${token}`); return res.redirect(`/reset/${token}`);
} }
@@ -97,6 +104,13 @@ module.exports = (users) => {
req.session.error = 'Reset link is invalid.'; req.session.error = 'Reset link is invalid.';
return res.redirect(`/reset`); return res.redirect(`/reset`);
} }
let strength = passwordStrength(password);
if (strength.id < 2) {
req.session.errMessage = `Password ${strength.value}`;
return res.redirect(`/reset/${token}`);
}
const hashPassword = await bcrypt.hash(password, 12); const hashPassword = await bcrypt.hash(password, 12);
await users.updateOne( await users.updateOne(
+30
View File
@@ -85,6 +85,8 @@ function lockAsset(assetId, icon) {
document.getElementById(`${key}-${assetId}`).disabled = true; document.getElementById(`${key}-${assetId}`).disabled = true;
}); });
if (icon === "Car" || icon === "Motorcycle") document.getElementById(`year-${assetId}`).disabled = true;
document.getElementById(`save-${assetId}`).disabled = true; document.getElementById(`save-${assetId}`).disabled = true;
document.getElementById(`save-${assetId}`).classList.remove("cursor-pointer"); document.getElementById(`save-${assetId}`).classList.remove("cursor-pointer");
document.getElementById(`save-${assetId}`).classList.add("cursor-not-allowed"); document.getElementById(`save-${assetId}`).classList.add("cursor-not-allowed");
@@ -105,6 +107,8 @@ function unlockAsset(assetId, icon) {
document.getElementById(`${key}-${assetId}`).disabled = false; document.getElementById(`${key}-${assetId}`).disabled = false;
}); });
if (icon === "Car" || icon === "Motorcycle") document.getElementById(`year-${assetId}`).disabled = false;
document.getElementById(`save-${assetId}`).disabled = false; document.getElementById(`save-${assetId}`).disabled = false;
document.getElementById(`save-${assetId}`).classList.remove("cursor-not-allowed"); document.getElementById(`save-${assetId}`).classList.remove("cursor-not-allowed");
document.getElementById(`save-${assetId}`).classList.add("cursor-pointer"); document.getElementById(`save-${assetId}`).classList.add("cursor-pointer");
@@ -133,6 +137,8 @@ function autoOpenCreate() {
* @param {string} assetId * @param {string} assetId
*/ */
function selectIcon(selectedIcon, assetId="") { function selectIcon(selectedIcon, assetId="") {
toggleYear(selectedIcon.value, assetId);
document.getElementById(`dropdown-icon-button${assetId != "" ? "-" : ""}${assetId}`).value = selectedIcon.value; document.getElementById(`dropdown-icon-button${assetId != "" ? "-" : ""}${assetId}`).value = selectedIcon.value;
document.getElementById(`icon${assetId != "" ? "-" : ""}${assetId}`).value = selectedIcon.value document.getElementById(`icon${assetId != "" ? "-" : ""}${assetId}`).value = selectedIcon.value
@@ -144,5 +150,29 @@ function selectIcon(selectedIcon, assetId="") {
`; `;
} }
/**
* toggleYear shows or hides year input
* @param {string} type of asset
*/
function toggleYear(type, assetId="") {
if (type === "Car" || type === "Motorcycle") {
if (assetId) {
document.getElementById(`year-${assetId}`).disabled = false;
document.getElementById(`year-modify-${assetId}`).style.display = 'block';
} else {
document.getElementById('year-input').disabled = false;
document.getElementById('year-create').style.display = 'block';
}
} else {
if (assetId) {
document.getElementById(`year-${assetId}`).disabled = true;
document.getElementById(`year-modify-${assetId}`).style.display = 'none';
} else {
document.getElementById('year-input').disabled = true;
document.getElementById('year-create').style.display = 'none';
}
}
}
resetRadio(); resetRadio();
autoOpenCreate(); autoOpenCreate();
-3
View File
@@ -1,3 +0,0 @@
let rate = document.getElementsByClassName("countryButton").value;
document.getElementById("exchange").innerHTML
+4 -4
View File
@@ -1,12 +1,12 @@
/** /**
* Function takes in a number and formats it to currency * Function takes in a number and formats it to currency
* @param {integer} * @param {number} value
* @returns nuber formatted in currency * @returns {string} number formatted in currency
*/ */
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
const number = document.querySelectorAll(".planGoal"); const number = document.querySelectorAll(".planGoal");
number.forEach(value => { number.forEach((value) => {
num = parseFloat(value.textContent); num = parseFloat(value.textContent);
if (num >= 999999) { if (num >= 999999) {
value.textContent = "$" + (num /= 1000000) + "M"; value.textContent = "$" + (num /= 1000000) + "M";
+67
View File
@@ -0,0 +1,67 @@
// Basic client-side form submission handler to process JSON response
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('editPlanForm');
const errorMessagesDiv = document.getElementById('formErrorMessages');
// Store initial form values
const initialValues = {
name: form.name.value,
retirementAge: form.retirementAge.value,
retirementExpenses: form.retirementExpenses.value,
retirementAssets: form.retirementAssets.value,
retirementLiabilities: form.retirementLiabilities.value
};
const resetButton = document.getElementById('resetButton');
resetButton.addEventListener('click', function() {
form.name.value = initialValues.name;
form.retirementAge.value = initialValues.retirementAge;
form.retirementExpenses.value = initialValues.retirementExpenses;
form.retirementAssets.value = initialValues.retirementAssets;
form.retirementLiabilities.value = initialValues.retirementLiabilities;
if (errorMessagesDiv) {
errorMessagesDiv.style.display = 'none'; // Hide error message on reset
errorMessagesDiv.textContent = '';
}
});
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) {
const actionUrl = form.action;
const urlParts = actionUrl.split('/');
const planId = urlParts[urlParts.length - 2];
window.location.href = `/plans/${planId}`;
} 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';
}
});
}
});
+1
View File
@@ -13,6 +13,7 @@ if(page !== "login" && page !== "signup" && page !== "forgotPassword" && page !=
factButton.addEventListener("click", () => { factButton.addEventListener("click", () => {
factMenu.classList.toggle("hidden"); factMenu.classList.toggle("hidden");
}); });
factSubmitButton.addEventListener("click", () => { factSubmitButton.addEventListener("click", () => {
fetch("/fact", { fetch("/fact", {
method: "POST", method: "POST",
+24
View File
@@ -1,9 +1,17 @@
/**
* getLocation gets the geolocation of user on page load.
*/
function getLocation() { function getLocation() {
if (navigator.geolocation) { if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(getLatestExchange, error); navigator.geolocation.getCurrentPosition(getLatestExchange, error);
} }
} }
/**
* update the currency exchange rate dropdown
* or display error
* @param {object} data
*/
function update(data) { function update(data) {
if (data.data.message != "error") { if (data.data.message != "error") {
document.getElementById("loading").style = "display: none"; document.getElementById("loading").style = "display: none";
@@ -48,6 +56,10 @@ function update(data) {
} }
} }
/**
* switchButton changes the selected exchange rate icon
* @param {element} clickedButton
*/
function switchButton(clickedButton) { function switchButton(clickedButton) {
document.getElementById("dropdown-country-button").value = clickedButton.value; document.getElementById("dropdown-country-button").value = clickedButton.value;
updateExchange(document.getElementById("dropdown-country-button").value); updateExchange(document.getElementById("dropdown-country-button").value);
@@ -60,10 +72,18 @@ function switchButton(clickedButton) {
`; `;
} }
/**
* updateExchange updates the exchange rate
* @param {number} exRate
*/
function updateExchange(exRate) { function updateExchange(exRate) {
document.getElementById("exchange").innerHTML = "$1.00 = $" +`${(1 * exRate).toFixed(2)}`; document.getElementById("exchange").innerHTML = "$1.00 = $" +`${(1 * exRate).toFixed(2)}`;
} }
/**
* getLatestExchange gets rates from API from a given location.
* @param {object} position coordinates
*/
async function getLatestExchange(position) { async function getLatestExchange(position) {
let lat = position.coords.latitude; let lat = position.coords.latitude;
let lon = position.coords.longitude; let lon = position.coords.longitude;
@@ -73,6 +93,10 @@ async function getLatestExchange(position) {
update(data); update(data);
} }
/**
* error displays error message
* @param {string} err
*/
function error(err) { function error(err) {
const data = { const data = {
data: { data: {
+8
View File
@@ -1,3 +1,7 @@
/**
* editPlan redirect
* @param {string} planId
*/
function editPlan(planId) { function editPlan(planId) {
if (planId) { if (planId) {
window.location.href = `/plans/${planId}/edit`; window.location.href = `/plans/${planId}/edit`;
@@ -7,6 +11,10 @@ function editPlan(planId) {
} }
} }
/**
* deletePlan handler
* @param {string} planId
*/
async function deletePlan(planId) { async function deletePlan(planId) {
if (!planId) { if (!planId) {
console.error('deletePlan called without a planId'); console.error('deletePlan called without a planId');
+85
View File
@@ -0,0 +1,85 @@
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');
let url = new URLSearchParams(window.location.search);
let modalValue = url.get('openModal');
if (modalValue === 'true') {
newPlanDialog.showModal();
}
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';
}
});
});
+53 -24
View File
@@ -1,5 +1,5 @@
const getRates = require("../util/exchangeRate"); const getRates = require("../util/exchangeRate");
const { calculateProgress, updatePlanProgressInDB, updateProgress } = require("../util/calculations"); const { calculateProgress, updatePlanProgressInDB, updateProgress, calculateTotalAssetValue } = require("../util/calculations");
const suggestions = require("../util/suggestions"); const suggestions = require("../util/suggestions");
const status = require("../util/statuses"); const status = require("../util/statuses");
const ObjectId = require('mongodb').ObjectId; const ObjectId = require('mongodb').ObjectId;
@@ -24,29 +24,33 @@ const getAssetSchema = (type) => {
assetSchema = joi.object({ assetSchema = joi.object({
type: joi.string().valid("other", "stock", "saving").required(), type: joi.string().valid("other", "stock", "saving").required(),
icon: joi.string().alphanum().required(), icon: joi.string().alphanum().required(),
name: joi.string().alphanum().min(3).max(30).required(), name: joi.string().min(3).max(30).required(),
value: joi.number().min(0).required(), value: joi.number().min(0).required(),
year: joi.number().min(1900).max(new Date().getFullYear()),
purchaseDate: joi.date().required(), purchaseDate: joi.date().required(),
description: joi.string().alphanum().max(240), description: joi.string().max(240).min(0),
id: joi.string().alphanum(), // May be passed when updating existing asset id: joi.string().alphanum(), // May be passed when updating existing asset
userId: joi.string().alphanum(),
}); });
break; break;
case "saving": case "saving":
assetSchema = joi.object({ assetSchema = joi.object({
type: joi.string().valid("other", "stock", "saving").required(), type: joi.string().valid("other", "stock", "saving").required(),
name: joi.string().alphanum().min(3).max(30).required(), name: joi.string().min(3).max(30).required(),
value: joi.number().min(0).required(), value: joi.number().min(0).required(),
id: joi.string().alphanum(), // May be passed when updating existing asset id: joi.string().alphanum(), // May be passed when updating existing asset
userId: joi.string().alphanum(),
}); });
break; break;
case "stock": case "stock":
assetSchema = joi.object({ assetSchema = joi.object({
type: joi.string().valid("other", "stock", "saving").required(), type: joi.string().valid("other", "stock", "saving").required(),
ticker: joi.string().alphanum().min(3).max(5).required(), ticker: joi.string().min(3).max(5).required(),
price: joi.number().min(0).required(), price: joi.number().min(0).required(),
quantity: joi.number().min(1).required(), quantity: joi.number().min(1).required(),
purchaseDate: joi.date().required(), purchaseDate: joi.date().required(),
id: joi.string().alphanum(), // May be passed when updating existing asset id: joi.string().alphanum(), // May be passed when updating existing asset
userId: joi.string().alphanum(),
}); });
break; break;
default: default:
@@ -105,18 +109,33 @@ module.exports = (middleware, users, plans, assets) => {
}); });
router.get('/assets', async (req, res) => { router.get('/assets', async (req, res) => {
if (!req.session.user.financialData || !req.session.user) {
req.session.errMessage = "Please complete your financial data before creating a plan.";
return res.status(status.Unauthorized).redirect('/questionnaire');
}
let userAssets = await assets.find({ userId: new ObjectId(req.session.userId) }).toArray(); let userAssets = await assets.find({ userId: new ObjectId(req.session.userId) }).toArray();
let totalUserAssetValue = await calculateTotalAssetValue(userAssets);
res.render('assets', { res.render('assets', {
user: req.session.user, user: req.session.user,
errMessage: req.session.errMessage, errMessage: req.session.errMessage,
assets: userAssets, assets: userAssets,
geoData: req.session.geoData, geoData: req.session.geoData,
totalUserAssetValue: totalUserAssetValue,
icons: icons, icons: icons,
}); });
return res.status(status.Ok); return res.status(status.Ok);
}); });
router.get('/plans', async (req, res) => { router.get('/plans', async (req, res) => {
if (!req.session.user.financialData || !req.session.user) {
req.session.errMessage = "Please complete your financial data before creating a plan.";
return res.status(status.Unauthorized).redirect('/questionnaire');
}
try { try {
const updatedUserPlans = await updateProgress(plans, assets, users, req.session.user._id); const updatedUserPlans = await updateProgress(plans, assets, users, req.session.user._id);
@@ -138,6 +157,7 @@ module.exports = (middleware, users, plans, assets) => {
try { try {
const planId = req.params.id; const planId = req.params.id;
let userAssets = await assets.find({ userId: new ObjectId(req.session.userId) }).toArray(); let userAssets = await assets.find({ userId: new ObjectId(req.session.userId) }).toArray();
let totalUserAssetValue = await calculateTotalAssetValue(userAssets);
if (!ObjectId.isValid(planId)) { if (!ObjectId.isValid(planId)) {
req.session.errMessage = "Invalid plan ID format."; req.session.errMessage = "Invalid plan ID format.";
@@ -156,6 +176,7 @@ module.exports = (middleware, users, plans, assets) => {
user: req.session.user, user: req.session.user,
plan: plan, plan: plan,
geoData: req.session.geoData, geoData: req.session.geoData,
totalUserAssetValue: totalUserAssetValue,
assets: userAssets, assets: userAssets,
progress: progress, progress: progress,
suggestions: await suggestions.generateSuggestions(), suggestions: await suggestions.generateSuggestions(),
@@ -168,21 +189,27 @@ module.exports = (middleware, users, plans, assets) => {
} }
}); });
router.get('/newPlan', (req, res) => { // router.get('/newPlan', (req, res) => {
// if (!req.session.user.financialData || !req.session.user) {
// req.session.errMessage = "Please complete your financial data before creating a plan.";
// return res.status(status.Unauthorized).redirect('/questionnaire');
// }
// const errMessage = req.session.errMessage;
// req.session.errMessage = "";
// res.render('newPlan', {
// user: req.session.user,
// errMessage: errMessage,
// geoData: req.session.geoData
// });
// });
router.post('/newPlan', async (req, res) => {
if (!req.session.user.financialData || !req.session.user) { if (!req.session.user.financialData || !req.session.user) {
req.session.errMessage = "Please complete your financial data before creating a plan."; req.session.errMessage = "Please complete your financial data before creating a plan.";
return res.status(status.Unauthorized).redirect('/questionnaire'); return res.status(status.Unauthorized).redirect('/questionnaire');
} }
const errMessage = req.session.errMessage;
req.session.errMessage = "";
res.render('newPlan', {
user: req.session.user,
errMessage: errMessage,
geoData: req.session.geoData
});
});
router.post('/newPlan', async (req, res) => {
const planSchema = joi.object({ const planSchema = joi.object({
name: joi.string().min(3).max(100).required(), name: joi.string().min(3).max(100).required(),
retirementAge: joi.number().min(18).max(120).required(), retirementAge: joi.number().min(18).max(120).required(),
@@ -449,16 +476,16 @@ module.exports = (middleware, users, plans, assets) => {
router.post("/updateAccount", async (req, res) => { router.post("/updateAccount", async (req, res) => {
const accountSchema = joi.object({ const accountSchema = joi.object({
email: joi.string().email(), email: joi.string().email({ minDomainSegments: 2, tlds: { allow: true } }),
name: joi.string().pattern(new RegExp('^[a-zA-Z]+$')).max(20), name: joi.string().pattern(new RegExp('^[a-zA-Z]+$')).max(20),
password: joi.string().alphanum().max(20).min(8), password: joi.string().max(20).min(8),
repassword: joi.string().alphanum().max(20).min(8), repassword: joi.string().max(20).min(8),
}); });
const valid = accountSchema.validate(req.body); const valid = accountSchema.validate(req.body);
if (valid.err) { if (valid.error) {
req.session.errMessage = "Invalid input", req.session.errMessage = "Invalid input:" + valid.error.details.map(d => d.message.replace(/"/g, '')).join(', ');
res.status(status.BadRequest); res.status(status.BadRequest);
return res.redirect("/profile"); return res.redirect("/profile");
} }
@@ -606,8 +633,8 @@ module.exports = (middleware, users, plans, assets) => {
const valid = assetSchema.validate(req.body); const valid = assetSchema.validate(req.body);
if (valid.err) { if (valid.error) {
req.session.errMessage = "Invalid input", req.session.errMessage = "Invalid input:" + valid.error.details.map(d => d.message.replace(/"/g, '')).join(', ');
res.status(status.BadRequest); res.status(status.BadRequest);
return res.redirect("/assets"); return res.redirect("/assets");
} }
@@ -624,6 +651,7 @@ module.exports = (middleware, users, plans, assets) => {
newAsset.value = newAsset.quantity * newAsset.price; newAsset.value = newAsset.quantity * newAsset.price;
newAsset.name = `${newAsset.ticker} Stock`; newAsset.name = `${newAsset.ticker} Stock`;
} }
if (type == "other" && newAsset.year != "") newAsset.year = parseInt(newAsset.year);
newAsset.value = parseFloat(newAsset.value); newAsset.value = parseFloat(newAsset.value);
newAsset.icon = type == "stock" ? "Stock" : type == "saving" ? "Coins" : newAsset.icon; newAsset.icon = type == "stock" ? "Stock" : type == "saving" ? "Coins" : newAsset.icon;
@@ -651,8 +679,8 @@ module.exports = (middleware, users, plans, assets) => {
const valid = assetSchema.validate(req.body); const valid = assetSchema.validate(req.body);
if (valid.err) { if (valid.error) {
req.session.errMessage = "Invalid input", req.session.errMessage = "Invalid input:" + valid.error.details.map(d => d.message.replace(/"/g, '')).join(', ');
res.status(status.BadRequest); res.status(status.BadRequest);
return res.redirect("/assets"); return res.redirect("/assets");
} }
@@ -670,6 +698,7 @@ module.exports = (middleware, users, plans, assets) => {
update.value = update.quantity * update.price; update.value = update.quantity * update.price;
update.name = `${update.ticker} Stock`; update.name = `${update.ticker} Stock`;
} }
if (type == "other" && update.year != "") update.year = parseInt(update.year);
update.value = parseFloat(update.value); update.value = parseFloat(update.value);
delete update.id; delete update.id;
delete update.userId; delete update.userId;
+43 -3
View File
@@ -12,7 +12,7 @@ async function calculatePlanProgress(plans, assets, userId) {
try { try {
const userAssets = await assets.find({ userId: new ObjectId(userId) }).toArray(); const userAssets = await assets.find({ userId: new ObjectId(userId) }).toArray();
const totalUserAssetValue = userAssets.reduce((total, asset) => total + asset.value, 0); const totalUserAssetValue = await calculateTotalAssetValue(userAssets);
let percentage = 0; let percentage = 0;
if (plans.retirementAssets > 0) { if (plans.retirementAssets > 0) {
@@ -42,6 +42,41 @@ async function updatePlanProgressInDB(planId, percentage, plans) {
} }
} }
async function calculateTotalAssetValue(assets) {
if (!assets || typeof assets.find !== 'function') {
console.error("Error with the assets collection");
return 0;
}
try {
let totalAssetValue = 0;
const today = new Date();
for (const asset of assets) {
if (asset.icon === "Motorcycle" || asset.icon === "Car") {
if (asset.value < 1000) {
totalAssetValue += asset.value;
} else {
const assetYear = new Date(asset.year).getFullYear();
const age = today.getFullYear() - assetYear;
let depreciatedValue = asset.value * Math.pow(0.85, age);
if (depreciatedValue < 1000) {
totalAssetValue += 1000;
} else {
totalAssetValue += depreciatedValue;
}
}
} else {
totalAssetValue += asset.value;
}
}
return totalAssetValue;
} catch (err) {
console.error("Error in calculateTotalAssetValue:", err);
return 0;
}
}
async function calculateProgress(plan, assets, users, userId) { async function calculateProgress(plan, assets, users, userId) {
if (!plan || typeof plan !== 'object') { if (!plan || typeof plan !== 'object') {
@@ -63,9 +98,10 @@ async function calculateProgress(plan, assets, users, userId) {
try { try {
const userAssets = await assets.find({ userId: new ObjectId(userId) }).toArray(); const userAssets = await assets.find({ userId: new ObjectId(userId) }).toArray();
const totalUserAssetValue = userAssets.reduce((total, asset) => total + asset.value, 0); const totalUserAssetValue = await calculateTotalAssetValue(userAssets);
const totalUserPlanValue = plan.retirementAssets; const totalUserPlanValue = plan.retirementAssets;
const userDoc = await users.findOne({ _id: new ObjectId(userId) }); const userDoc = await users.findOne({ _id: new ObjectId(userId) });
if (!userDoc || !userDoc.dob) { if (!userDoc || !userDoc.dob) {
@@ -93,6 +129,10 @@ async function calculateProgress(plan, assets, users, userId) {
const percentageCalculated = (totalUserAssetValue / (totalUserPlanValue + totalCostOfRetirement)) * 100; const percentageCalculated = (totalUserAssetValue / (totalUserPlanValue + totalCostOfRetirement)) * 100;
if (monthsUntilRetirement <= 0) {
return { monthlyInvestment: 0, totalCostOfRetirement: totalCostOfRetirement, monthsUntilRetirement: 0, yearsRetired: yearsRetired, yearsUntilRetirement: 0, percentage: 0 };
}
const progress = {}; const progress = {};
progress.monthlyInvestment = Math.round(monthlyInvestment); progress.monthlyInvestment = Math.round(monthlyInvestment);
@@ -125,4 +165,4 @@ async function updateProgress(plans, assets, users, userId) {
module.exports = { calculatePlanProgress, updatePlanProgressInDB, calculateProgress, updateProgress}; module.exports = { calculatePlanProgress, updatePlanProgressInDB, calculateTotalAssetValue, calculateProgress, updateProgress};
+11 -8
View File
@@ -10,26 +10,29 @@
<!-- Main display --> <!-- Main display -->
<section> <section>
<div class="mt-8 max-w-md mx-auto bg-white px-8 py-6 rounded-lg shadow-md flex flex-col justify-center"> <div class="mt-2 max-w-md mx-auto bg-white px-8 py-6 rounded-lg shadow-md flex flex-col justify-center">
<div class="flex flex-row justify-between text-right"> <div class="flex flex-row justify-between text-right">
<h1 class="text-2xl font-bold tracking-tight leading-none">Assets:</h1> <h1 class="text-lg font-medium">Assets:</h1>
<h1 class="text-xl font-medium tracking-tight leading-none"><%= assets.length %></h1> <h1 class="text-lg font-medium"><%= assets.length %></h1>
</div> </div>
<div class="flex flex-row justify-between text-right mt-6"> <div class="flex flex-row justify-between text-right mt-6">
<h1 class="text-2xl font-bold tracking-tight leading-none">Total Value:</h1> <h1 class="text-lg font-medium">Total Value:</h1>
<h1 class="text-xl font-medium tracking-tight leading-none"> <h1 class="text-lg font-medium">
<!-- sum of assets array values and formated as proper dollar amount --> <!-- sum of assets array values and formated as proper dollar amount -->
$<%= assets.reduce((total, e) => total + e.value, 0).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) %> <%- new Intl.NumberFormat('en-US', { style: 'currency', currency: geoData.currency ? geoData.currency : 'CAD' }).format(totalUserAssetValue) %>
</h1> </h1>
</div> </div>
</div> </div>
<div class="w-md my-6 max-w-md mx-auto px-4 flex flex-row justify-between"> <div class="w-md my-6 max-w-md mx-auto px-4 flex flex-row justify-between">
<a href="/home" class="text-center w-sm py-2 mx-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"> <a href="/home" class="text-center w-sm py-2 mx-2 px-4 border
border-transparent rounded-md shadow-sm text-sm font-medium
text-white bg-blue-600 hover:bg-blue-700 focus:outline-none">
Back to Dashboard Back to Dashboard
</a> </a>
<button <button
class="text-center w-sm py-2 py-2 mx-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" class="text-center w-sm py-2 py-2 mx-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600
hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 cursor-pointer"
onclick="document.getElementById('create-asset-modal').showModal()" onclick="document.getElementById('create-asset-modal').showModal()"
> >
Add New Asset Add New Asset
+21 -3
View File
@@ -3,19 +3,37 @@
<main class="pt-24 pb-14"> <main class="pt-24 pb-14">
<!-- Buttons --> <!-- Buttons -->
<div class="flex justify-end gap-4 px-5 py-6"> <div class="flex md:justify-end justify-center gap-4 px-5 py-6 md:py-0">
<a href="/assets?popup" <a href="/assets?popup"
class="text-center cursor-pointer min-w-[150px] bg-green-600 px-4 py-2 text-white rounded hover:bg-green-800" id="addAss" class="text-center cursor-pointer min-w-[150px] bg-green-600 px-4 py-2 text-white rounded hover:bg-green-800"
type="button">Add Asset</a> type="button">Add Asset</a>
<a href="/newPlan" <a href="/plans?openModal=true"
class="text-center cursor-pointer min-w-[150px] bg-green-600 px-4 py-2 text-white rounded hover:bg-green-800" class="text-center cursor-pointer min-w-[150px] bg-green-600 px-4 py-2 text-white rounded hover:bg-green-800"
type="button">Create new plan </a> type="button">Create new plan </a>
</div> </div>
<div class="w-full"> <div class="w-full">
<div class="mb-12 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-y-4 gap-x-6"> <div class="mb-12 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-y-4 gap-x-6">
<% if (plans.length === 0) { %>
<div class="flex justify-center md:col-start-2">
<div class="col-span-1 mt-25">
<div class="w-86 p-4 bg-white rounded-lg shadow-xl border border-slate-200">
<h1 class="text-2xl block text-center font-semibold text-slate-700">Don't have any retirement plans? </h1>
<div class="mt-4 flex justify-center items-center">
<div class="mt-4 flex justify-center">
<a href="/plans" class="cursor-pointer border-2 border-blue-600 bg-blue-600 text-white py-2 px-6
rounded-md hover:bg-blue-700 hover:border-blue-700 font-semibold
transition-colors duration-200 focus:outline-none ">Make one</a>
</div>
</div>
</div>
</div>
</div>
<% } else %>
<% plans.forEach(plan=> { %> <% plans.forEach(plan=> { %>
<%- include('./partials/dashboardBox.ejs', {thing: plan}) %> <%- include('./partials/dashboardBox.ejs', {thing: plan}) %>
<% }); %> <% }); %>
</div> </div>
</div> </div>
</main> </main>
+20 -60
View File
@@ -4,98 +4,58 @@
<main class="container mx-auto p-4 pt-28"> <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"> <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> <h2 class="text-2xl font-bold text-center text-white mb-8">Edit Plan: <%= plan.name %></h2>
<p class="text-sm text-gray-400 text-left mb-8"><span class="text-red-500">*</span> Indicates a required field</p>
<form id="editPlanForm" action="/plans/<%= plan._id %>/edit" method="POST" class="space-y-6"> <form id="editPlanForm" action="/plans/<%= plan._id %>/edit" method="POST" class="space-y-6">
<div> <div>
<label for="name" class="block text-sm font-medium text-gray-300">Plan Name</label> <label for="name" class="block text-sm font-medium text-gray-300">Plan Name <span class="text-red-500">*</span></label>
<input type="text" name="name" id="name" value="<%= plan.name %>" required <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"> 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-blue-500 focus:border-blue-500 sm:text-sm placeholder-gray-400">
</div> </div>
<div> <div>
<label for="retirementAge" class="block text-sm font-medium text-gray-300">Desired Retirement Age</label> <label for="retirementAge" class="block text-sm font-medium text-gray-300">Desired Retirement Age <span class="text-red-500">*</span></label>
<input type="number" name="retirementAge" id="retirementAge" value="<%= plan.retirementAge %>" required min="18" <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"> 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-blue-500 focus:border-blue-500 sm:text-sm placeholder-gray-400">
</div> </div>
<div> <div>
<label for="retirementExpenses" class="block text-sm font-medium text-gray-300">Estimated Monthly Expenses at Retirement (<%= geoData.currency || 'CAD' %>)</label> <label for="retirementExpenses" class="block text-sm font-medium text-gray-300">Estimated Monthly Expenses at Retirement (<%= geoData.currency || 'CAD' %>) <span class="text-red-500">*</span></label>
<input type="number" name="retirementExpenses" id="retirementExpenses" value="<%= plan.retirementExpenses %>" required min="0" step="any" <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"> 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-blue-500 focus:border-blue-500 sm:text-sm placeholder-gray-400">
</div> </div>
<div> <div>
<label for="retirementAssets" class="block text-sm font-medium text-gray-300">Target Retirement Assets (<%= geoData.currency || 'CAD' %>)</label> <label for="retirementAssets" class="block text-sm font-medium text-gray-300">Target Retirement Assets (<%= geoData.currency || 'CAD' %>) <span class="text-red-500">*</span></label>
<input type="number" name="retirementAssets" id="retirementAssets" value="<%= plan.retirementAssets %>" required min="0" step="any" <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"> 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-blue-500 focus:border-blue-500 sm:text-sm placeholder-gray-400">
</div> </div>
<div> <div>
<label for="retirementLiabilities" class="block text-sm font-medium text-gray-300">Estimated Monthly Liabilities at Retirement (<%= geoData.currency || 'CAD' %>)</label> <label for="retirementLiabilities" class="block text-sm font-medium text-gray-300">Estimated Monthly Liabilities at Retirement (<%= geoData.currency || 'CAD' %>) <span class="text-red-500">*</span></label>
<input type="number" name="retirementLiabilities" id="retirementLiabilities" value="<%= plan.retirementLiabilities %>" required min="0" step="any" <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"> 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-blue-500 focus:border-blue-500 sm:text-sm placeholder-gray-400">
</div> </div>
<div id="formErrorMessages" class="text-red-400 text-sm my-2" style="display: none;"></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"> <div class="flex items-center justify-center 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" <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"> class="inline-flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-blue-500">
Save Changes Save Changes
</button> </button>
<button type="button" id="resetButton" 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-blue-500">
Reset
</button>
<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-blue-500">
Cancel
</a>
</div> </div>
</form> </form>
</div> </div>
</main> </main>
<script> <script src="/static/scripts/editPlanModals.js"></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/navBar') %>
<%- include('./partials/scriptLoader') %> <%- include('./partials/scriptLoader') %>
+3 -3
View File
@@ -1,7 +1,7 @@
<%- include("./partials/fileHeader") %> <%- include("./partials/fileHeader") %>
<%- include("./partials/headerStart") %> <%- include("./partials/headerStart") %>
<main class="pt-12 bg-slate-100 min-h-screen"> <main class="pt-12">
<div class="flex justify-center items-center py-10"> <div class="flex justify-center items-center py-10">
<form action="/auth/resetPass" method="POST"> <form action="/auth/resetPass" method="POST">
<div class="w-96 p-8 bg-white rounded-lg shadow-xl border border-slate-200"> <div class="w-96 p-8 bg-white rounded-lg shadow-xl border border-slate-200">
@@ -12,7 +12,7 @@
<div class="mt-4"> <div class="mt-4">
<label for="email" class="block text-base mb-2 text-slate-600">Email</label> <label for="email" class="block text-base mb-2 text-slate-600">Email</label>
<input type="text" id="email" name="email" <input type="text" id="email" name="email"
class="border border-slate-300 focus:border-indigo-500 w-full rounded-md text-base px-3 py-2 focus:outline-none focus:ring-1 focus:ring-indigo-500 transition-colors duration-200" class="border border-slate-300 focus:border-blue-500 w-full rounded-md text-base px-3 py-2 focus:outline-none focus:ring-1 focus:ring-blue-500 transition-colors duration-200"
placeholder="Enter Email" /> placeholder="Enter Email" />
</div> </div>
<% } else { %> <% } else { %>
@@ -31,7 +31,7 @@
<% if (!reset) { %> <% if (!reset) { %>
<div class="mt-6"> <div class="mt-6">
<button type="submit" <button type="submit"
class="border-2 border-indigo-600 bg-indigo-600 text-white py-2 w-full rounded-md hover:bg-indigo-700 hover:border-indigo-700 font-semibold transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">Send Reset Link</button> class="border-2 border-blue-600 bg-blue-600 text-white py-2 w-full rounded-md hover:bg-blue-700 hover:border-blue-700 font-semibold transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">Send Reset Link</button>
</div> </div>
<% } %> <% } %>
</div> </div>
+13 -12
View File
@@ -6,17 +6,18 @@
<form action="/login" method="POST"> <form action="/login" method="POST">
<div class="w-96 p-8 bg-white rounded-lg shadow-xl border border-slate-200"> <div class="w-96 p-8 bg-white rounded-lg shadow-xl border border-slate-200">
<h1 class="text-3xl block text-center font-semibold text-slate-700">Login</h1> <h1 class="text-3xl block text-center font-semibold text-slate-700">Login</h1>
<hr class="mt-4 mb-6 border-slate-300"> <hr class="mt-4 mb-4 border-slate-300">
<p class="text-sm text-gray-400 text-left mb-4"><span class="text-red-500">*</span> Indicates a required field</p>
<div class="mt-4"> <div class="mt-4">
<label for="email" class="block text-base mb-2 text-slate-600">Email</label> <label for="email" class="block text-base mb-2 text-slate-600">Email <span class="text-red-500">*</span></label>
<input type="text" id="email" name="email" <input required type="text" id="email" name="email"
class="border border-slate-300 focus:border-indigo-500 w-full rounded-md text-base px-3 py-2 focus:outline-none focus:ring-1 focus:ring-indigo-500 transition-colors duration-200" class="border border-slate-300 focus:border-blue-500 w-full rounded-md text-base px-3 py-2 focus:outline-none focus:ring-1 focus:ring-blue-500 transition-colors duration-200"
placeholder="Enter Email" /> placeholder="Enter Email" value="<%= typeof email != 'undefined' ? email : '' %>"/>
</div> </div>
<div class="mt-4"> <div class="mt-4">
<label for="password" class="block text-base mb-2 text-slate-600">Password</label> <label for="password" class="block text-base mb-2 text-slate-600">Password <span class="text-red-500">*</span></label>
<input type="password" id="password" name="password" <input required type="password" id="password" name="password"
class="border border-slate-300 focus:border-indigo-500 w-full rounded-md text-base px-3 py-2 focus:outline-none focus:ring-1 focus:ring-indigo-500 transition-colors duration-200" class="border border-slate-300 focus:border-blue-500 w-full rounded-md text-base px-3 py-2 focus:outline-none focus:ring-1 focus:ring-blue-500 transition-colors duration-200"
placeholder="Enter Password" /> placeholder="Enter Password" />
</div> </div>
<% if (errMessage && errMessage.length > 0) { %> <% if (errMessage && errMessage.length > 0) { %>
@@ -26,20 +27,20 @@
<% } %> <% } %>
<div class="mt-4 flex justify-between items-center"> <div class="mt-4 flex justify-between items-center">
<div class="flex items-center"> <div class="flex items-center">
<input type="checkbox" id="rememberMe" class="h-4 w-4 text-indigo-600 border-slate-300 rounded focus:ring-indigo-500"> <input type="checkbox" id="rememberMe" class="h-4 w-4 text-blue-600 border-slate-300 rounded focus:ring-blue-500">
<label for="rememberMe" class="ml-2 text-sm text-slate-600">Remember me</label> <label for="rememberMe" class="ml-2 text-sm text-slate-600">Remember me</label>
</div> </div>
<div> <div>
<a href="/forgotPassword" class="text-sm text-indigo-600 hover:text-indigo-500 font-semibold transition-colors duration-200">Forgot Password? </a> <a href="/forgotPassword" class="text-sm text-blue-600 hover:text-blue-500 font-semibold transition-colors duration-200">Forgot Password? </a>
</div> </div>
</div> </div>
<div class="mt-6"> <div class="mt-6">
<button type="submit" <button type="submit"
class="border-2 border-indigo-600 bg-indigo-600 text-white py-2 w-full rounded-md hover:bg-indigo-700 hover:border-indigo-700 font-semibold transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">Login</button> class="border-2 border-blue-600 bg-blue-600 text-white py-2 w-full rounded-md hover:bg-blue-700 hover:border-blue-700 font-semibold transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">Login</button>
</div> </div>
<div class="mt-6 text-center"> <div class="mt-6 text-center">
<span class="text-sm text-slate-600">Don't have an account?</span> <span class="text-sm text-slate-600">Don't have an account?</span>
<a href="/signup" class="text-sm text-indigo-600 hover:text-indigo-500 font-semibold ml-1 transition-colors duration-200">Register</a> <a href="/signup" class="text-sm text-blue-600 hover:text-blue-500 font-semibold ml-1 transition-colors duration-200">Register</a>
</div> </div>
</div> </div>
</form> </form>
+4 -1
View File
@@ -4,7 +4,10 @@
<!-- Modal content --> <!-- Modal content -->
<div class="relative p-4 text-center rounded-lg shadow bg-gray-800 sm:p-5"> <div class="relative p-4 text-center rounded-lg shadow bg-gray-800 sm:p-5">
<form method="dialog"> <form method="dialog">
<button type="submit" class="text-gray-500 absolute top-2.5 right-2.5 bg-transparent rounded-lg text-sm p-1.5 ml-auto inline-flex items-center hover:bg-gray-600 hover:text-white"> <button
type="submit"
class="text-gray-500 absolute top-2.5 right-2.5 bg-transparent rounded-lg text-sm p-1.5 ml-auto inline-flex items-center hover:bg-gray-600 hover:text-white"
>
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg> <svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
<span class="sr-only">Close modal</span> <span class="sr-only">Close modal</span>
</button> </button>
+110 -20
View File
@@ -8,7 +8,8 @@
style="width: 100%;" style="width: 100%;"
onclick="lockAsset('<%= asset._id %>', '<%= asset.icon %>')" onclick="lockAsset('<%= asset._id %>', '<%= asset.icon %>')"
type="submit" type="submit"
class="text-center w-sm py-2 py-2 mx-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" class="text-center w-sm py-2 py-2 mx-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white
bg-blue-600 hover:bg-blue-700 focus:outline-none "
> >
Close Close
</button> </button>
@@ -30,7 +31,13 @@
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
<% if (asset.type == "other") { %> <% if (asset.type == "other") { %>
<div class="flex items-center"> <div class="flex items-center">
<button id="dropdown-icon-button-<%= asset._id %>" value="" data-dropdown-toggle="dropdown-icons-<%= asset._id %>" class="shrink-0 z-10 inline-flex items-center py-2.5 px-4 text-sm font-medium text-center border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none" type="button"> <button
id="dropdown-icon-button-<%= asset._id %>"
value=""
data-dropdown-toggle="dropdown-icons-<%= asset._id %>"
class="shrink-0 z-10 inline-flex items-center py-2.5 px-4 text-sm font-medium text-center border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"
type="button"
>
<img src="/static/svgs/icons/<%= asset.icon %>.svg" class="h-4 w-4 me-2" alt="<%= asset.icon %>"> <%= asset.icon %> <img src="/static/svgs/icons/<%= asset.icon %>.svg" class="h-4 w-4 me-2" alt="<%= asset.icon %>"> <%= asset.icon %>
<svg class="w-2.5 h-2.5 ms-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6"> <svg class="w-2.5 h-2.5 ms-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/> <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
@@ -51,7 +58,11 @@
</div> </div>
</div> </div>
<% } else { %> <% } else { %>
<button disabled class="inline-flex items-center py-2.5 px-10 text-sm font-medium text-center border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none" type="button"> <button
disabled
class="inline-flex items-center py-2.5 px-10 text-sm font-medium text-center border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"
type="button"
>
<img src="/static/svgs/icons/<%= asset.icon %>.svg" class="h-4 w-4" alt="<%= asset.icon %>"> <img src="/static/svgs/icons/<%= asset.icon %>.svg" class="h-4 w-4" alt="<%= asset.icon %>">
</button> </button>
<% } %> <% } %>
@@ -61,7 +72,7 @@
<button <button
id="edit-<%= asset._id %>" id="edit-<%= asset._id %>"
onclick="unlockAsset('<%= asset._id %>')" onclick="unlockAsset('<%= asset._id %>')"
class="py-3 px-8 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" class="py-3 px-8 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-pointer"
> >
Edit Edit
</button> </button>
@@ -76,42 +87,121 @@
<input id="icon-<%= asset._id %>" name="icon" type="text" hidden value="<%= asset.icon %>"> <input id="icon-<%= asset._id %>" name="icon" type="text" hidden value="<%= asset.icon %>">
<% if (asset.type != "stock") { %> <% if (asset.type != "stock") { %>
<label for="name" class="block text-sm font-medium text-gray-700 mt-6">Name</label> <label for="name" class="block text-sm font-medium text-gray-700 mt-6">Name <span class="text-red-500">*</span></label>
<input id="name-<%= asset._id %>" disabled type="text" name="name" value="<%= asset.name %>" class="mt-1 mb-2 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 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> <input
id="name-<%= asset._id %>"
disabled
type="text"
name="name"
value="<%= asset.name %>"
class="mt-1 mb-2 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"
>
<% } else { %> <% } else { %>
<label for="ticker" class="block text-sm font-medium text-gray-700 mt-2">Stock Ticker</label> <label for="ticker" class="block text-sm font-medium text-gray-700 mt-2">Stock Ticker <span class="text-red-500">*</span></label>
<input id="ticker-<%= asset._id %>" disabled type="text" name="ticker" value="<%= asset.ticker %>" class="mt-1 mb-2 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 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> <input
id="ticker-<%= asset._id %>"
disabled
type="text"
name="ticker"
value="<%= asset.ticker %>"
class="mt-1 mb-2 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"
>
<% } %> <% } %>
<hr class="h-px mt-6 mb-4 bg-gray-500 border-0 mx-10"> <hr class="h-px mt-6 mb-4 bg-gray-500 border-0 mx-10">
<% if (asset.type != "stock") { %> <% if (asset.type != "stock") { %>
<label for="value" class="block text-sm font-medium text-gray-700">Value</label> <label for="value" class="block text-sm font-medium text-gray-700">Value <span class="text-red-500">*</span></label>
<input id="value-<%= asset._id %>" disabled type="number" name="value" value="<%= asset.value %>" 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 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> <input
<% } else { %> id="value-<%= asset._id %>"
<label for="price" class="block text-sm font-medium text-gray-700 mt-2">Price per Share</label> disabled
<input id="price-<%= asset._id %>" disabled type="number" name="price" value="<%= asset.price %>" 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 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> type="number"
step=".01"
name="value"
value="<%= asset.value %>"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"
>
<label for="quantity" class="block text-sm font-medium text-gray-700 mt-2">Quantity of Shares</label> <div
<input id="quantity-<%= asset._id %>" disabled type="number" name="quantity" value="<%= asset.quantity %>" 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 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> id="year-modify-<%= asset._id %>"
<% if (asset.icon != "Car" && asset.icon != "Motorcycle") { %>
style="display: none;"
<% } %>
>
<label for="year" class="block text-sm font-medium text-gray-700 mt-2">Year <span class="text-red-500">*</span></label>
<input
id="year-<%= asset._id %>"
disabled
required
placeholder="2000"
min="1900"
max="<% new Date().getFullYear() %>"
type="number"
name="year"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"
value="<%= asset.year %>"
>
</div>
<% } else { %>
<label for="price" class="block text-sm font-medium text-gray-700 mt-2">Price per Share <span class="text-red-500">*</span></label>
<input
id="price-<%= asset._id %>"
disabled
type="number"
step=".01"
name="price"
value="<%= asset.price %>"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"
>
<label for="quantity" class="block text-sm font-medium text-gray-700 mt-2">Quantity of Shares <span class="text-red-500">*</span></label>
<input
id="quantity-<%= asset._id %>"
disabled
type="number"
name="quantity"
value="<%= asset.quantity %>"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"
>
<% } %> <% } %>
<% if (asset.type == "other") { %> <% if (asset.type == "other") { %>
<label for="description" class="block text-sm font-medium text-gray-700 mt-2">Description</label> <label for="description" class="block text-sm font-medium text-gray-700 mt-2">Description</label>
<textarea disabled maxlength="240" id="description-<%= asset._id %>" name="description" rows="4" cols="50" 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 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"><%= asset.description %></textarea> <textarea
disabled
maxlength="240"
id="description-<%= asset._id %>"
name="description"
rows="4"
cols="50"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"
><%= asset.description %></textarea>
<% } %> <% } %>
<% if (asset.type != "saving") { %> <% if (asset.type != "saving") { %>
<label for="purchaseDate" class="block text-sm font-medium text-gray-700 mt-2">Purchased on</label> <label for="purchaseDate" class="block text-sm font-medium text-gray-700 mt-2">Purchased on <span class="text-red-500">*</span></label>
<input id="purchaseDate-<%= asset._id %>" disabled type="date" name="purchaseDate" value="<%= asset.purchaseDate %>" 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 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> <input
id="purchaseDate-<%= asset._id %>"
disabled
type="date"
name="purchaseDate"
value="<%= asset.purchaseDate %>"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"
>
<% } %> <% } %>
<br> <br>
<small class="block text-sm font-medium text-gray-700">Last Modified: <%= asset.updatedAt %></small> <% let options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }; %>
<small class="block text-sm font-medium text-gray-700">Last Modified: <%= asset.updatedAt.toLocaleDateString("en-US", options); %></small>
<br> <br>
<div> <div>
<button id="save-<%= asset._id %>" disabled 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 cursor-not-allowed"> <button
id="save-<%= asset._id %>"
disabled
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-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-not-allowed"
>
Save Save
</button> </button>
</div> </div>
+6 -2
View File
@@ -1,6 +1,10 @@
<!-- Clickable asset list item --> <!-- Clickable asset list item -->
<div class="mt-4 max-w-md mx-auto bg-white rounded-lg shadow-md"> <div class="mt-4 max-w-md mx-auto bg-white rounded-lg shadow-md">
<button onclick="document.getElementById('<%= asset._id %>-modal').showModal()" style="width: 100%; height: 100%;" class="px-4 py-4 cursor-pointer"> <button
onclick="document.getElementById('<%= asset._id %>-modal').showModal()"
style="width: 100%; height: 100%;"
class="px-4 py-4 cursor-pointer"
>
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
<div class="mx-2"> <div class="mx-2">
@@ -10,7 +14,7 @@
</div> </div>
<div> <div>
<h3><strong> <h3><strong>
$<%= asset.value.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) %> <%- new Intl.NumberFormat('en-US', { style: 'currency', currency: geoData.currency ? geoData.currency : 'CAD' }).format(asset.value) %>
</strong></h3> </strong></h3>
</div> </div>
</div> </div>
+157 -31
View File
@@ -6,26 +6,49 @@
<button <button
onclick="resetAll()" onclick="resetAll()"
type="submit" type="submit"
class="text-center py-2 mx-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" class="text-center py-2 mx-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none cursor-pointer"
> >
Cancel Cancel
</button> </button>
</form> </form>
</div> </div>
<p class="text-sm text-gray-400 text-left mb-4"><span class="text-red-500">*</span> Indicates a required field</p>
<div class="my-4"> <div class="my-4">
<label class="block text-sm font-medium text-gray-700">Asset Type</label> <label class="block text-sm font-medium text-gray-700">Asset Type</label>
<div class="mt-1 space-x-4"> <div class="mt-1 space-x-4">
<label class="inline-flex items-center"> <label class="inline-flex items-center">
<input checked onclick="selectAssetForm('create-other-asset-form')" id="asset-other" type="radio" name="type" value="other" class="form-radio h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500 cursor-pointer"> <input
checked
onclick="selectAssetForm('create-other-asset-form')"
id="asset-other"
type="radio"
name="type"
value="other"
class="form-radio h-4 w-4 text-blue-600 border-gray-300 focus:ring-blue-500 cursor-pointer"
>
<span class="ml-2 text-sm text-gray-700">Other Assets</span> <span class="ml-2 text-sm text-gray-700">Other Assets</span>
</label> </label>
<label class="inline-flex items-center"> <label class="inline-flex items-center">
<input onclick="selectAssetForm('create-saving-asset-form')" id="asset-saving" type="radio" name="type" value="saving" class="form-radio h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500 cursor-pointer"> <input
onclick="selectAssetForm('create-saving-asset-form')"
id="asset-saving"
type="radio"
name="type"
value="saving"
class="form-radio h-4 w-4 text-blue-600 border-gray-300 focus:ring-blue-500 cursor-pointer"
>
<span class="ml-2 text-sm text-gray-700">Savings</span> <span class="ml-2 text-sm text-gray-700">Savings</span>
</label> </label>
<label class="inline-flex items-center"> <label class="inline-flex items-center">
<input onclick="selectAssetForm('create-stock-asset-form')" id="asset-stock" type="radio" name="type" value="stock" class="form-radio h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500 cursor-pointer"> <input
onclick="selectAssetForm('create-stock-asset-form')"
id="asset-stock"
type="radio"
name="type"
value="stock"
class="form-radio h-4 w-4 text-blue-600 border-gray-300 focus:ring-blue-500 cursor-pointer"
>
<span class="ml-2 text-sm text-gray-700">Stocks</span> <span class="ml-2 text-sm text-gray-700">Stocks</span>
</label> </label>
</div> </div>
@@ -38,7 +61,12 @@
<label for="name" class="block text-sm font-medium text-gray-700 mt-2">Select an Icon</label> <label for="name" class="block text-sm font-medium text-gray-700 mt-2">Select an Icon</label>
<div class="flex items-center"> <div class="flex items-center">
<button id="dropdown-icon-button" value="" data-dropdown-toggle="dropdown-icons" class="shrink-0 z-10 inline-flex items-center py-2.5 px-4 text-sm font-medium text-center border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" type="button"> <button
id="dropdown-icon-button"
data-dropdown-toggle="dropdown-icons"
class="shrink-0 z-10 inline-flex items-center py-2.5 px-4 text-sm font-medium text-center border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
type="button"
>
<img src="/static/svgs/icons/Other.svg" class="h-4 w-4 me-2" alt="Other"> Other <img src="/static/svgs/icons/Other.svg" class="h-4 w-4 me-2" alt="Other"> Other
<svg class="w-2.5 h-2.5 ms-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6"> <svg class="w-2.5 h-2.5 ms-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/> <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
@@ -48,7 +76,13 @@
<ul id="dropdown" class="py-2 text-sm text-gray-200" aria-labelledby="dropdown-icon-button"> <ul id="dropdown" class="py-2 text-sm text-gray-200" aria-labelledby="dropdown-icon-button">
<% icons.forEach(icon => { %> <% icons.forEach(icon => { %>
<li> <li>
<button onclick="selectIcon(this)" type="button" value="<%= icon %>" class="inline-flex w-full px-4 py-2 text-sm text-gray-200 hover:bg-gray-600 hover:text-white" role="menuitem"> <button
onclick="selectIcon(this)"
type="button"
value="<%= icon %>"
class="inline-flex w-full px-4 py-2 text-sm text-gray-200 hover:bg-gray-600 hover:text-white"
role="menuitem"
>
<span class="inline-flex items-center"> <span class="inline-flex items-center">
<img src="/static/svgs/icons/<%= icon %>.svg" class="h-4 w-4 me-2" alt="<%= icon %>"> (<%= icon %>) <img src="/static/svgs/icons/<%= icon %>.svg" class="h-4 w-4 me-2" alt="<%= icon %>"> (<%= icon %>)
</span> </span>
@@ -59,29 +93,74 @@
</div> </div>
</div> </div>
<label for="name" class="block text-sm font-medium text-gray-700 mt-2">Asset Name</label> <label for="name" class="block text-sm font-medium text-gray-700 mt-2">Asset Name <span class="text-red-500">*</span></label>
<input required id="name" type="text" name="name" minlength="3" maxlength="30" 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"> <input
required
id="name"
type="text"
name="name"
minlength="3"
maxlength="30"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
<label for="value" class="block text-sm font-medium text-gray-700 mt-2">Asset Value</label> <div style="display: none;" id="year-create">
<input required placeholder="CAD" min="0" id="value" type="number" name="value" 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"> <label for="year" class="block text-sm font-medium text-gray-700 mt-2">Year <span class="text-red-500">*</span></label>
<input
id="year-input"
disabled
required
placeholder="2000"
min="1900"
max="<% new Date().getFullYear() %>"
type="number"
name="year"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
</div>
<label for="purchaseDate" class="block text-sm font-medium text-gray-700 mt-2">Date of Purchase</label> <label for="value" class="block text-sm font-medium text-gray-700 mt-2">Asset Value <span class="text-red-500">*</span></label>
<input required type="date" id="purchaseDate" name="purchaseDate" 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"> <input
required
placeholder="CAD"
min="0"
step=".01"
id="value"
type="number"
name="value"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
<label for="purchaseDate" class="block text-sm font-medium text-gray-700 mt-2">Date of Purchase <span class="text-red-500">*</span></label>
<input
required
type="date"
id="purchaseDate"
name="purchaseDate"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
<label for="description" class="block text-sm font-medium text-gray-700 mt-2">Description</label> <label for="description" class="block text-sm font-medium text-gray-700 mt-2">Description</label>
<textarea maxlength="240" id="description" name="description" rows="4" cols="50" 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"></textarea> <textarea
maxlength="240"
id="description"
name="description"
rows="4"
cols="50"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
></textarea>
<div class="mt-6 flex flex-row justify-between"> <div class="mt-6 flex flex-row justify-between">
<button <button
type="reset" type="reset"
onclick="document.getElementById('create-other-asset-form').reset()" onclick="document.getElementById('create-other-asset-form').reset()"
class="text-center w-sm py-2 mx-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" class="text-center w-sm py-2 mx-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-pointer"
> >
Clear Clear
</button> </button>
<button <button
type="submit" type="submit"
class="text-center w-sm py-2 mx-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" class="text-center w-sm py-2 mx-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-pointer"
> >
Create Create
</button> </button>
@@ -92,23 +171,40 @@
<form style="display: none;" method="POST" action="/createAsset" id="create-saving-asset-form"> <form style="display: none;" method="POST" action="/createAsset" id="create-saving-asset-form">
<input style="display: none;" value="saving" name="type" type="text"> <input style="display: none;" value="saving" name="type" type="text">
<label for="name" class="block text-sm font-medium text-gray-700 mt-2">Savings Name</label> <label for="name" class="block text-sm font-medium text-gray-700 mt-2">Savings Name <span class="text-red-500">*</span></label>
<input required id="name" type="text" name="name" minlength="3" maxlength="30" 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"> <input
required
id="name"
type="text"
name="name"
minlength="3"
maxlength="30"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
<label for="value" class="block text-sm font-medium text-gray-700 mt-2">Savings Value</label> <label for="value" class="block text-sm font-medium text-gray-700 mt-2">Savings Value <span class="text-red-500">*</span></label>
<input required placeholder="CAD" min="0" id="value" type="number" name="value" 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"> <input
required
placeholder="CAD"
min="0"
step=".01"
id="value"
type="number"
name="value"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
<div class="mt-6 flex flex-row justify-between"> <div class="mt-6 flex flex-row justify-between">
<button <button
type="reset" type="reset"
onclick="document.getElementById('create-saving-asset-form').reset()" onclick="document.getElementById('create-saving-asset-form').reset()"
class="text-center w-sm py-2 mx-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" class="text-center w-sm py-2 mx-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-pointer"
> >
Clear Clear
</button> </button>
<button <button
type="submit" type="submit"
class="text-center w-sm py-2 mx-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" class="text-center w-sm py-2 mx-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-pointer"
> >
Create Create
</button> </button>
@@ -119,29 +215,59 @@
<form style="display: none;" method="POST" action="/createAsset" id="create-stock-asset-form"> <form style="display: none;" method="POST" action="/createAsset" id="create-stock-asset-form">
<input style="display: none;" value="stock" name="type" type="text"> <input style="display: none;" value="stock" name="type" type="text">
<label for="ticker" class="block text-sm font-medium text-gray-700 mt-2">Stock Ticker</label> <label for="ticker" class="block text-sm font-medium text-gray-700 mt-2">Stock Ticker <span class="text-red-500">*</span></label>
<input required id="ticker" type="text" name="ticker" minlength="3" maxlength="5" 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"> <input
required
id="ticker"
type="text"
name="ticker"
minlength="3"
maxlength="5"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
<label for="price" class="block text-sm font-medium text-gray-700 mt-2">Price per Share</label> <label for="price" class="block text-sm font-medium text-gray-700 mt-2">Price per Share <span class="text-red-500">*</span></label>
<input required placeholder="CAD" min="0" id="price" type="number" name="price" 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"> <input
required
placeholder="CAD"
min="0"
step=".01"
id="price"
type="number"
name="price"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
<label for="quantity" class="block text-sm font-medium text-gray-700 mt-2">Quantity of Shares</label> <label for="quantity" class="block text-sm font-medium text-gray-700 mt-2">Quantity of Shares <span class="text-red-500">*</span></label>
<input required type="number" min="1" id="quantity" name="quantity" 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"> <input
required
type="number"
min="1"
id="quantity"
name="quantity"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
<label for="purchaseDate" class="block text-sm font-medium text-gray-700 mt-2">Date of Purchase</label> <label for="purchaseDate" class="block text-sm font-medium text-gray-700 mt-2">Date of Purchase <span class="text-red-500">*</span></label>
<input required type="date" id="purchaseDate" name="purchaseDate" 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"> <input
required
type="date"
id="purchaseDate"
name="purchaseDate"
class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
>
<div class="mt-6 flex flex-row justify-between"> <div class="mt-6 flex flex-row justify-between">
<button <button
type="reset" type="reset"
onclick="document.getElementById('create-stock-asset-form').reset()" onclick="document.getElementById('create-stock-asset-form').reset()"
class="text-center w-sm py-2 mx-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" class="text-center w-sm py-2 mx-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-pointer"
> >
Clear Clear
</button> </button>
<button <button
type="submit" type="submit"
class="text-center w-sm py-2 mx-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" class="text-center w-sm py-2 mx-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-pointer"
> >
Create Create
</button> </button>
+3 -3
View File
@@ -5,10 +5,10 @@
class="relative flex flex-col bg-white text-gray-700 shadow-md rounded-xl w-full h-full overflow-hidden min-h-[175px]"> class="relative flex flex-col bg-white text-gray-700 shadow-md rounded-xl w-full h-full overflow-hidden min-h-[175px]">
<!-- Header Section --> <!-- Header Section -->
<div class="p-4 flex items-center justify-between border-b "> <div class="p-4 flex items-center justify-between border-b ">
<p class="text-xl font-semibold text-blue-gray-700 truncate" title="<%= thing.name %>"> <p class="text-2xl font-bold tracking-tight text-blue-gray-700 truncate" title="<%= thing.name %>">
<%= thing.name %> <%= thing.name %>
</p> </p>
<h4 class="planGoal text-xl font-semibold whitespace-nowrap"> <h4 class="planGoal text-2xl font-bold tracking-tight whitespace-nowrap">
<%= thing.retirementAssets %> <%= thing.retirementAssets %>
</h4> </h4>
</div> </div>
@@ -17,7 +17,7 @@
<div class="mt-2"> <div class="mt-2">
<div class="p-4 mt-auto border-gray-900"> <div class="p-4 mt-auto border-gray-900">
<div class="flex justify-between items-center mb-1"> <div class="flex justify-between items-center mb-1">
<p class="text-sm font-medium text-blue-gray-600"> <p class="text-lg font-bold tracking-tight text-blue-gray-600">
Progress: Progress:
</p> </p>
<p class="text-sm font-semibold justify-between text-green-500"> <p class="text-sm font-semibold justify-between text-green-500">
+3 -3
View File
@@ -34,7 +34,7 @@
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-400">Total Value:</label> <label class="block text-sm font-medium text-gray-400">Total Value:</label>
<p class="mt-1 text-md text-white"><%- new Intl.NumberFormat('en-US', { style: 'currency', currency: geoData.currency ? geoData.currency : 'CAD' }).format(assets.reduce((total, asset) => total + asset.value, 0)) %></p> <p class="mt-1 text-md text-white"><%- new Intl.NumberFormat('en-US', { style: 'currency', currency: geoData.currency ? geoData.currency : 'CAD' }).format(totalUserAssetValue) %></p>
</div> </div>
</div> </div>
</div> </div>
@@ -48,7 +48,7 @@
<p class="mt-1 text-md text-white"><%= plan.retirementAge %></p> <p class="mt-1 text-md text-white"><%= plan.retirementAge %></p>
</div> </div>
<div> <div>
<label class="block text-sm font-medium0 text-gray-400">Target Monthly Expenses (Minus Liabilities):</label> <label class="block text-sm font-medium text-gray-400">Target Monthly Expenses (Minus Liabilities):</label>
<p class="mt-1 text-md text-white"><%- new Intl.NumberFormat('en-US', { style: 'currency', currency: geoData.currency ? geoData.currency : 'CAD' }).format(plan.retirementExpenses) %></p> <p class="mt-1 text-md text-white"><%- new Intl.NumberFormat('en-US', { style: 'currency', currency: geoData.currency ? geoData.currency : 'CAD' }).format(plan.retirementExpenses) %></p>
</div> </div>
<div> <div>
@@ -88,7 +88,7 @@
</div> </div>
</div> </div>
<div class="flex items-center justify-center space-x-2"> <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-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-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> <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>
+16 -94
View File
@@ -21,41 +21,42 @@
<!-- New Plan Modal - Styled like createAsset.ejs --> <!-- 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"> <dialog id="newPlanDialog" class="w-lg mx-auto bg-white p-8 mt-10 rounded-lg shadow-md">
<!-- Header: Title and Cancel button --> <!-- Header: Title and Cancel button -->
<div class="flex flex-row justify-between items-center mb-4"> <div class="flex flex-row justify-between items-center">
<h3 class="text-lg font-bold text-gray-900">Create New Retirement Plan</h3> <h3 class="text-lg font-bold text-gray-900">Create New Retirement Plan</h3>
<form method="dialog" id="cancelNewPlanDialogForm"> <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> <button type="submit" class="py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-pointer">Cancel</button>
</form> </form>
</div> </div>
<!-- Form Content --> <!-- Form Content -->
<div> <div>
<form id="newPlanForm" class="space-y-4"> <form id="newPlanForm" class="space-y-4">
<p class="text-sm text-gray-400 text-left mb-4"><span class="text-red-500">*</span> Indicates a required field</p>
<div> <div>
<label for="name" class="block text-sm font-medium text-gray-700 text-left">Plan Name</label> <label for="name" class="block text-sm font-medium text-gray-700 text-left">Plan Name <span class="text-red-500">*</span></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> <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-blue-500 focus:border-blue-500 sm:text-sm" required>
</div> </div>
<div> <div>
<label for="retirementAge" class="block text-sm font-medium text-gray-700 text-left">Desired Retirement Age</label> <label for="retirementAge" class="block text-sm font-medium text-gray-700 text-left">Desired Retirement Age <span class="text-red-500">*</span></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> <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-blue-500 focus:border-blue-500 sm:text-sm" required>
</div> </div>
<div> <div>
<label for="retirementExpenses" class="block text-sm font-medium text-gray-700 text-left">Estimated Monthly Retirement Expenses</label> <label for="retirementExpenses" class="block text-sm font-medium text-gray-700 text-left">Estimated Monthly Retirement Expenses <span class="text-red-500">*</span></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> <input title="Ongoing spending. Rent, bills, subscriptions, etc." 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-blue-500 focus:border-blue-500 sm:text-sm" required>
</div> </div>
<div> <div>
<label for="retirementAssets" class="block text-sm font-medium text-gray-700 text-left">Estimated Retirement Assets</label> <label for="retirementAssets" class="block text-sm font-medium text-gray-700 text-left">Estimated Retirement Assets <span class="text-red-500">*</span></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> <input title="Owned valuables. Physical items, investments, etc." 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-blue-500 focus:border-blue-500 sm:text-sm" required>
</div> </div>
<div> <div>
<label for="retirementLiabilities" class="block text-sm font-medium text-gray-700 text-left">Estimated Retirement Liabilities</label> <label for="retirementLiabilities" class="block text-sm font-medium text-gray-700 text-left">Estimated Retirement Liabilities <span class="text-red-500">*</span></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> <input title="Owed amounts. Loans, credit card balance, etc." 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-blue-500 focus:border-blue-500 sm:text-sm" required>
</div> </div>
<div id="newPlanError" class="text-red-500 text-sm mt-2" style="display: none;"></div> <div id="newPlanError" class="text-red-500 text-sm mt-2" style="display: none;"></div>
<!-- Form Buttons: Save Plan and Reset --> <!-- Form Buttons: Save Plan and Reset -->
<div class="mt-6 flex flex-row justify-between"> <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 type="button" id="resetNewPlanFormBtn" class="py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-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> <button id="submitNewPlanBtn" type="submit" class="py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">Save Plan</button>
</div> </div>
</form> </form>
</div> </div>
@@ -64,84 +65,5 @@
<%- include("./partials/navBar") %> <%- include("./partials/navBar") %>
<%- include("./partials/scriptLoader") %> <%- include("./partials/scriptLoader") %>
<script> <script src="/static/scripts/planModals.js"></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") %> <%- include("./partials/footer") %>
+21 -19
View File
@@ -51,24 +51,24 @@
<div class="max-w-md mx-auto bg-white p-8 mb-10 rounded-lg shadow-md"> <div class="max-w-md mx-auto bg-white p-8 mb-10 rounded-lg shadow-md">
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
<h3 class="pt-2 pr-2 pb-2">Account settings</h3> <h3 class="pt-2 pr-2 pb-2">Account settings</h3>
<button id="edit-account" onclick="unlockAccount()" 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">Edit</button> <button id="edit-account" onclick="unlockAccount()" class="py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">Edit</button>
</div> </div>
<form action="/updateAccount" method="post" class="space-y-4 mt-4" id="account-form"> <form action="/updateAccount" method="post" class="space-y-4 mt-4" id="account-form">
<label for="email" class="block text-sm font-medium text-gray-700">Email</label> <label for="email" class="block text-sm font-medium text-gray-700">Email</label>
<input id="email" disabled type="email" name="email" value="<%= user.email %>" 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 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> <input id="email" disabled type="email" name="email" value="<%= user.email %>" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none">
<label for="name" class="block text-sm font-medium text-gray-700">Name</label> <label for="name" class="block text-sm font-medium text-gray-700">Name</label>
<input id="name" disabled type="text" name="name" value="<%= user.name %>" 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 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> <input id="name" disabled type="text" name="name" value="<%= user.name %>" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none">
<label for="password" class="block text-sm font-medium text-gray-700">New password</label> <label for="password" class="block text-sm font-medium text-gray-700">New password</label>
<input id="password" disabled type="password" name="password" minlength="8" 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 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> <input id="password" disabled type="password" name="password" minlength="8" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none">
<label for="repassword" class="block text-sm font-medium text-gray-700">Confirm new password</label> <label for="repassword" class="block text-sm font-medium text-gray-700">Confirm new password</label>
<input id="repassword" disabled type="password" name="repassword" 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 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> <input id="repassword" disabled type="password" name="repassword" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none">
<div> <div>
<button id="save-account" disabled 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 cursor-not-allowed">Save</button> <button id="save-account" disabled 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-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-not-allowed">Save</button>
</div> </div>
</form> </form>
</div> </div>
@@ -78,7 +78,7 @@
<div class="flex flex-row justify-between"> <div class="flex flex-row justify-between">
<h3 class="pt-2 pr-2 pb-2">Personal information</h3> <h3 class="pt-2 pr-2 pb-2">Personal information</h3>
<% if (user.financialData) { %> <% if (user.financialData) { %>
<button id="edit-personal" onclick="unlockPersonal()" 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">Edit</button> <button id="edit-personal" onclick="unlockPersonal()" class="py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">Edit</button>
<% } %> <% } %>
</div> </div>
@@ -91,17 +91,19 @@
id="dob" id="dob"
type="date" type="date"
name="dob" name="dob"
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 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"
<% let year = typeof(user.dob) == "object" ? user.dob.getFullYear() : user.dob.split('-')[0]; %> <% let year = typeof(user.dob) == "object" ? user.dob.getFullYear() : user.dob.split('-')[0]; %>
<% let month = typeof(user.dob) == "object" ? user.dob.getMonth() : user.dob.split('-')[1] - 1; %> <% let month = typeof(user.dob) == "object" ? user.dob.getMonth() : user.dob.split('-')[1] - 1; %>
<% let day = typeof(user.dob) == "object" ? user.dob.getDate() + 1 : user.dob.split('-')[2].split('T')[0];%> <% let day = typeof(user.dob) == "object" ? user.dob.getDate() + 1 : user.dob.split('-')[2].split('T')[0];%>
<% let d = new Date(parseInt(year), parseInt(month), parseInt(day)); %> <% let d = new Date(parseInt(year), parseInt(month), parseInt(day)); %>
value="<%= d.getFullYear() + "-" + ("0"+(d.getMonth()+1)).slice(-2) + "-" + ("0" + d.getDate()).slice(-2); %>" value="<%= d.getFullYear() + "-" + ("0"+(d.getMonth()+1)).slice(-2) + "-" + ("0" + d.getDate()).slice(-2); %>"
<% let d1 = new Date(); %>
max="<%= (d1.getFullYear() - 18) + "-" +("0"+(d1.getMonth()+1)).slice(-2) + "-" + ("0" + d1.getDate()).slice(-2); %>"
> >
</div> </div>
<div> <div>
<label for="education" class="block text-sm font-medium text-gray-700">Education</label> <label for="education" class="block text-sm font-medium text-gray-700">Education</label>
<select disabled id="education" name="education" id="education" class="mt-1 block w-full px-3 py-2 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> <select disabled id="education" name="education" id="education" class="mt-1 block w-full px-3 py-2 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none">
<option <%= user.education == "primary" ? 'selected' : '' %> value="primary">Primary (Elementary)</option> <option <%= user.education == "primary" ? 'selected' : '' %> value="primary">Primary (Elementary)</option>
<option <%= user.education == "secondary" ? 'selected' : '' %> value="secondary">Secondary (High School)</option> <option <%= user.education == "secondary" ? 'selected' : '' %> value="secondary">Secondary (High School)</option>
<option <%= user.education == "tertiary" ? 'selected' : '' %> value="tertiary">Tertiary (College/University)</option> <option <%= user.education == "tertiary" ? 'selected' : '' %> value="tertiary">Tertiary (College/University)</option>
@@ -112,19 +114,19 @@
<label class="block text-sm font-medium text-gray-700">Marital Status</label> <label class="block text-sm font-medium text-gray-700">Marital Status</label>
<div class="mt-1 space-x-4"> <div class="mt-1 space-x-4">
<label class="inline-flex items-center"> <label class="inline-flex items-center">
<input disabled <%= user.maritalStatus == "single" ? 'checked' : '' %> id="ms-single" type="radio" name="maritalStatus" value="single" class="form-radio h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> <input disabled <%= user.maritalStatus == "single" ? 'checked' : '' %> id="ms-single" type="radio" name="maritalStatus" value="single" class="form-radio h-4 w-4 text-blue-600 border-gray-300 focus:ring-blue-500 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none">
<span class="ml-2 text-sm text-gray-700">Single</span> <span class="ml-2 text-sm text-gray-700">Single</span>
</label> </label>
<label class="inline-flex items-center"> <label class="inline-flex items-center">
<input disabled <%= user.maritalStatus == "married" ? 'checked' : '' %> id="ms-married" type="radio" name="maritalStatus" value="married" class="form-radio h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> <input disabled <%= user.maritalStatus == "married" ? 'checked' : '' %> id="ms-married" type="radio" name="maritalStatus" value="married" class="form-radio h-4 w-4 text-blue-600 border-gray-300 focus:ring-blue-500 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none">
<span class="ml-2 text-sm text-gray-700">Married</span> <span class="ml-2 text-sm text-gray-700">Married</span>
</label> </label>
<label class="inline-flex items-center"> <label class="inline-flex items-center">
<input disabled <%= user.maritalStatus == "divorced" ? 'checked' : '' %> id="ms-divorced" type="radio" name="maritalStatus" value="divorced" class="form-radio h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> <input disabled <%= user.maritalStatus == "divorced" ? 'checked' : '' %> id="ms-divorced" type="radio" name="maritalStatus" value="divorced" class="form-radio h-4 w-4 text-blue-600 border-gray-300 focus:ring-blue-500 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none">
<span class="ml-2 text-sm text-gray-700">Divorced</span> <span class="ml-2 text-sm text-gray-700">Divorced</span>
</label> </label>
<label class="inline-flex items-center"> <label class="inline-flex items-center">
<input disabled <%= user.maritalStatus == "widowed" ? 'checked' : '' %> id="ms-widowed" type="radio" name="maritalStatus" value="widowed" class="form-radio h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> <input disabled <%= user.maritalStatus == "widowed" ? 'checked' : '' %> id="ms-widowed" type="radio" name="maritalStatus" value="widowed" class="form-radio h-4 w-4 text-blue-600 border-gray-300 focus:ring-blue-500 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none">
<span class="ml-2 text-sm text-gray-700">Widowed</span> <span class="ml-2 text-sm text-gray-700">Widowed</span>
</label> </label>
</div> </div>
@@ -132,31 +134,31 @@
<div> <div>
<label for="income" class="block text-sm font-medium text-gray-700">Annual Gross Income</label> <label for="income" class="block text-sm font-medium text-gray-700">Annual Gross Income</label>
<input disabled value="<%= user.income %>" id="income" type="number" name="income" 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 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> <input disabled value="<%= user.income %>" id="income" type="number" name="income" 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-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none">
</div> </div>
<div> <div>
<label for="expenses" class="block text-sm font-medium text-gray-700">Monthly Expenses</label> <label for="expenses" class="block text-sm font-medium text-gray-700">Monthly Expenses</label>
<input disabled value="<%= user.expenses %>" id="expenses" type="number" name="expenses" min="0" placeholder="e.g., 2000" 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 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> <input disabled value="<%= user.expenses %>" id="expenses" type="number" name="expenses" min="0" placeholder="e.g., 2000" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none">
</div> </div>
<div> <div>
<label for="assets" class="block text-sm font-medium text-gray-700">Net Worth</label> <label for="assets" class="block text-sm font-medium text-gray-700">Net Worth</label>
<input disabled value="<%= user.assets %>" id="assets" type="number" name="assets" min="0" placeholder="Estimated Net Worth" 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 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> <input disabled value="<%= user.assets %>" id="assets" type="number" name="assets" min="0" placeholder="Estimated Net Worth" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none">
</div> </div>
<div> <div>
<label for="liabilities" class="block text-sm font-medium text-gray-700">Liabilities</label> <label for="liabilities" class="block text-sm font-medium text-gray-700">Liabilities</label>
<input disabled value="<%= user.liabilities %>" id="liabilities" type="number" name="liabilities" min="0" placeholder="Estimated Liabilities" 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 disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none"> <input disabled value="<%= user.liabilities %>" id="liabilities" type="number" name="liabilities" min="0" placeholder="Estimated Liabilities" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 disabled:shadow-none">
</div> </div>
<div> <div>
<button id="save-personal" disabled 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 cursor-not-allowed">Save</button> <button id="save-personal" disabled 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-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-not-allowed">Save</button>
</div> </div>
</form> </form>
<% } else { %> <% } else { %>
<div> <div>
<a href="/questionnaire?profile" 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 cursor-pointer">Answer Questionnare</a> <a href="/questionnaire?profile" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 cursor-pointer">Answer Questionnare</a>
</div> </div>
<% } %> <% } %>
+20 -19
View File
@@ -2,17 +2,18 @@
<%- include("./partials/header") %> <%- include("./partials/header") %>
<main class="container mx-auto p-4 pt-28"> <main class="container mx-auto p-4 pt-28">
<h2 class="text-xl text-white font-semibold mb-4">Welcome: <%= user.name %></h2>
<div class="max-w-md mx-auto bg-white p-8 mb-10 rounded-lg shadow-md"> <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">Financial Questionnaire</h3> <h3 class="text-lg font-medium mb-6">Financial Questionnaire</h3>
<p class="text-sm text-gray-400 text-left mb-4"><span class="text-red-500">*</span> Indicates a required field</p>
<form action="/questionnaire" method="post" class="space-y-4"> <form action="/questionnaire" method="post" class="space-y-4">
<div> <div>
<label for="dob" class="block text-sm font-medium text-gray-700">Date of Birth</label> <label for="dob" class="block text-sm font-medium text-gray-700">Date of Birth <span class="text-red-500">*</span></label>
<input type="date" name="dob" 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"> <% let d = new Date(); %>
<input max="<%= (d.getFullYear() - 18) + "-" + ("0"+(d.getMonth()+1)).slice(-2) + "-" + ("0" + d.getDate()).slice(-2); %>" type="date" name="dob" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
</div> </div>
<div> <div>
<label for="education" class="block text-sm font-medium text-gray-700">Education</label> <label for="education" class="block text-sm font-medium text-gray-700">Education <span class="text-red-500">*</span></label>
<select name="education" id="education" class="mt-1 block w-full px-3 py-2 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"> <select name="education" id="education" class="mt-1 block w-full px-3 py-2 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
<option value="primary">Primary (Elementary)</option> <option value="primary">Primary (Elementary)</option>
<option value="secondary">Secondary (High School)</option> <option value="secondary">Secondary (High School)</option>
<option value="tertiary">Tertiary (College/University)</option> <option value="tertiary">Tertiary (College/University)</option>
@@ -20,49 +21,49 @@
</select> </select>
</div> </div>
<div> <div>
<label class="block text-sm font-medium text-gray-700">Marital Status</label> <label class="block text-sm font-medium text-gray-700">Marital Status <span class="text-red-500">*</span></label>
<div class="mt-1 space-x-4"> <div class="mt-1 space-x-4">
<label class="inline-flex items-center"> <label class="inline-flex items-center">
<input type="radio" name="maritalStatus" value="single" class="form-radio h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500"> <input type="radio" name="maritalStatus" value="single" class="form-radio h-4 w-4 text-blue-600 border-gray-300 focus:ring-blue-500">
<span class="ml-2 text-sm text-gray-700">Single</span> <span class="ml-2 text-sm text-gray-700">Single</span>
</label> </label>
<label class="inline-flex items-center"> <label class="inline-flex items-center">
<input type="radio" name="maritalStatus" value="married" class="form-radio h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500"> <input type="radio" name="maritalStatus" value="married" class="form-radio h-4 w-4 text-blue-600 border-gray-300 focus:ring-blue-500">
<span class="ml-2 text-sm text-gray-700">Married</span> <span class="ml-2 text-sm text-gray-700">Married</span>
</label> </label>
<label class="inline-flex items-center"> <label class="inline-flex items-center">
<input type="radio" name="maritalStatus" value="divorced" class="form-radio h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500"> <input type="radio" name="maritalStatus" value="divorced" class="form-radio h-4 w-4 text-blue-600 border-gray-300 focus:ring-blue-500">
<span class="ml-2 text-sm text-gray-700">Divorced</span> <span class="ml-2 text-sm text-gray-700">Divorced</span>
</label> </label>
<label class="inline-flex items-center"> <label class="inline-flex items-center">
<input type="radio" name="maritalStatus" value="widowed" class="form-radio h-4 w-4 text-indigo-600 border-gray-300 focus:ring-indigo-500"> <input type="radio" name="maritalStatus" value="widowed" class="form-radio h-4 w-4 text-blue-600 border-gray-300 focus:ring-blue-500">
<span class="ml-2 text-sm text-gray-700">Widowed</span> <span class="ml-2 text-sm text-gray-700">Widowed</span>
</label> </label>
</div> </div>
</div> </div>
<div> <div>
<label for="income" class="block text-sm font-medium text-gray-700">Annual Gross Income</label> <label for="income" class="block text-sm font-medium text-gray-700">Annual Gross Income <span class="text-red-500">*</span></label>
<input type="number" name="income" 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"> <input type="number" name="income" 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-blue-500 focus:border-blue-500 sm:text-sm">
</div> </div>
<div> <div>
<label for="expenses" class="block text-sm font-medium text-gray-700">Monthly Expenses</label> <label for="expenses" class="block text-sm font-medium text-gray-700">Monthly Expenses <span class="text-red-500">*</span></label>
<input type="number" name="expenses" min="0" placeholder="e.g., 2000" 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"> <input type="number" name="expenses" min="0" placeholder="e.g., 2000" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
</div> </div>
<div> <div>
<label for="assets" class="block text-sm font-medium text-gray-700">Net Worth</label> <label for="assets" class="block text-sm font-medium text-gray-700">Net Worth <span class="text-red-500">*</span></label>
<input type="number" name="assets" min="0" placeholder="Estimated Net Worth" 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"> <input type="number" name="assets" min="0" placeholder="Estimated Net Worth" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
</div> </div>
<div> <div>
<label for="liabilities" class="block text-sm font-medium text-gray-700">Liabilities</label> <label for="liabilities" class="block text-sm font-medium text-gray-700">Liabilities <span class="text-red-500">*</span></label>
<input type="number" name="liabilities" min="0" placeholder="Estimated Liabilities" 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"> <input type="number" name="liabilities" min="0" placeholder="Estimated Liabilities" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm">
</div> </div>
<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">Submit Questionnaire</button> <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-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">Submit Questionnaire</button>
</div> </div>
</form> </form>
</div> </div>
+1 -1
View File
@@ -26,7 +26,7 @@
<% } %> <% } %>
<div class="mt-5"> <div class="mt-5">
<button type="submit" <button type="submit"
class="cursor-pointer border-2 border-gray-600 bg-blue-600 text-white py-1 w-full rounded-md hover:bg-transparent hover:text-indigo-700 font-semibold">Change class="cursor-pointer border-2 border-gray-600 bg-blue-600 text-white py-1 w-full rounded-md hover:bg-transparent hover:text-blue-700 font-semibold">Change
Password</button> Password</button>
</div> </div>
</div> </div>
+18 -17
View File
@@ -6,27 +6,28 @@
<form action="/signup" method="POST"> <form action="/signup" method="POST">
<div class="w-96 p-8 bg-white rounded-lg shadow-xl border border-slate-200"> <div class="w-96 p-8 bg-white rounded-lg shadow-xl border border-slate-200">
<h1 class="text-3xl block text-center font-semibold text-slate-700">Signup</h1> <h1 class="text-3xl block text-center font-semibold text-slate-700">Signup</h1>
<hr class="mt-4 mb-6 border-slate-300"> <hr class="mt-4 mb-4 border-slate-300">
<p class="text-sm text-gray-400 text-left mb-4"><span class="text-red-500">*</span> Indicates a required field</p>
<div class="mt-4"> <div class="mt-4">
<label for="name" class="block text-base mb-2 text-slate-600">Name</label> <label for="name" class="block text-base mb-2 text-slate-600">Name <span class="text-red-500">*</span></label>
<input type="text" id="name" name="name" <input required type="text" id="name" name="name"
class="border border-slate-300 focus:border-indigo-500 w-full rounded-md text-base px-3 py-2 focus:outline-none focus:ring-1 focus:ring-indigo-500 transition-colors duration-200" class="border border-slate-300 focus:border-blue-500 w-full rounded-md text-base px-3 py-2 focus:outline-none focus:ring-1 focus:ring-blue-500 transition-colors duration-200"
placeholder="Enter Name" /> placeholder="Enter Name" value="<%= typeof name != 'undefined' ? name : '' %>"/>
</div> </div>
<div class="mt-4"> <div class="mt-4">
<label for="email" class="block text-base mb-2 text-slate-600">Email</label> <label for="email" class="block text-base mb-2 text-slate-600">Email <span class="text-red-500">*</span></label>
<input type="email" id="email" name="email" <input required type="email" id="email" name="email"
class="border border-slate-300 focus:border-indigo-500 w-full rounded-md text-base px-3 py-2 focus:outline-none focus:ring-1 focus:ring-indigo-500 transition-colors duration-200" class="border border-slate-300 focus:border-blue-500 w-full rounded-md text-base px-3 py-2 focus:outline-none focus:ring-1 focus:ring-blue-500 transition-colors duration-200"
placeholder="Enter Email" /> placeholder="Enter Email" value="<%= typeof email != 'undefined' ? email : '' %>"/>
</div> </div>
<div class="mt-4"> <div class="mt-4">
<label for="password" class="block text-base mb-2 text-slate-600">Password</label> <label for="password" class="block text-base mb-2 text-slate-600">Password <span class="text-red-500">*</span></label>
<input type="password" id="password" name="password" <input required type="password" id="password" name="password"
class="border border-slate-300 focus:border-indigo-500 w-full rounded-md text-base px-3 py-2 focus:outline-none focus:ring-1 focus:ring-indigo-500 transition-colors duration-200" class="border border-slate-300 focus:border-blue-500 w-full rounded-md text-base px-3 py-2 focus:outline-none focus:ring-1 focus:ring-blue-500 transition-colors duration-200"
placeholder="Enter Password" /> placeholder="Enter Password" />
<label for="repassword" class="block text-base mb-2 mt-3 text-slate-600">Re-type Password</label> <label for="repassword" class="block text-base mb-2 mt-3 text-slate-600">Re-type Password <span class="text-red-500">*</span></label>
<input type="password" id="repassword" name="repassword" <input required type="password" id="repassword" name="repassword"
class="border border-slate-300 focus:border-indigo-500 w-full rounded-md text-base px-3 py-2 focus:outline-none focus:ring-1 focus:ring-indigo-500 transition-colors duration-200" class="border border-slate-300 focus:border-blue-500 w-full rounded-md text-base px-3 py-2 focus:outline-none focus:ring-1 focus:ring-blue-500 transition-colors duration-200"
placeholder="Enter Password" /> placeholder="Enter Password" />
</div> </div>
<% if (errMessage && errMessage.length > 0) { %> <% if (errMessage && errMessage.length > 0) { %>
@@ -36,11 +37,11 @@
<% } %> <% } %>
<div class="mt-6"> <div class="mt-6">
<button type="submit" <button type="submit"
class="border-2 border-indigo-600 bg-indigo-600 text-white py-2 w-full rounded-md hover:bg-indigo-700 hover:border-indigo-700 font-semibold transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">Signup</button> class="border-2 border-blue-600 bg-blue-600 text-white py-2 w-full rounded-md hover:bg-blue-700 hover:border-blue-700 font-semibold transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2">Signup</button>
</div> </div>
<div class="mt-6 text-center"> <div class="mt-6 text-center">
<span class="text-sm text-slate-600">Already have an account?</span> <span class="text-sm text-slate-600">Already have an account?</span>
<a href="/login" class="text-sm text-indigo-600 hover:text-indigo-500 font-semibold ml-1 transition-colors duration-200">Login</a> <a href="/login" class="text-sm text-blue-600 hover:text-blue-500 font-semibold ml-1 transition-colors duration-200">Login</a>
</div> </div>
</div> </div>
</form> </form>