Compare commits
51
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
343a4f8275 | ||
|
|
0129315523 | ||
|
|
d82bfd5b56
|
||
|
|
a7bb955c8a | ||
|
|
19081e3bd3
|
||
|
|
7269814501
|
||
|
|
16f267bcd4 | ||
|
|
8998283552 | ||
|
|
5b6ce3b09d | ||
|
|
e45b7ca946
|
||
|
|
c0e2d01ea6
|
||
|
|
3d55a2b0d0 | ||
|
|
ec309fbafd | ||
|
|
c1adb31657 | ||
|
|
05fa5558e3 | ||
|
|
d2e69f230f | ||
|
|
0672981e69 | ||
|
|
5b147d4f46 | ||
|
|
d6425043cf
|
||
|
|
cf3d3738f1
|
||
|
|
cc6fd937db | ||
|
|
b12d4bebe1
|
||
|
|
7c09df0924
|
||
|
|
44e674c7c9
|
||
|
|
7c9e92345d
|
||
|
|
17e3bdf4f4 | ||
|
|
da0bfea655
|
||
|
|
7fe94e1934
|
||
|
|
7fd7fa28ff | ||
|
|
163cb73b9d
|
||
|
|
9a61d362be
|
||
|
|
db392d1878
|
||
|
|
e50047b26a
|
||
|
|
5bc26e334e | ||
|
|
ce9345fad9
|
||
|
|
90d507e33c
|
||
|
|
dd9a0a1027
|
||
|
|
231aca9ec5 | ||
|
|
796dc245b2
|
||
|
|
3dc1faaed5
|
||
|
|
5b3d5cdce1 | ||
|
|
07b8325a14 | ||
|
|
c0174c2b22 | ||
|
|
08397a1c89 | ||
|
|
273009f91f | ||
|
|
436e242cf1 | ||
|
|
dd1ad02b32 | ||
|
|
50bd21b164 | ||
|
|
ef858bcf00 | ||
|
|
de99301738 | ||
|
|
7c34094a93 |
No files matched your search
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
res.render('login', { errMessage: req.session.errMessage });
|
|
||||||
|
if (req.query.email) {
|
||||||
|
res.render('login', {
|
||||||
|
errMessage: req.session.errMessage,
|
||||||
|
email: req.query.email
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.render('login', { errMessage: req.session.errMessage });
|
||||||
|
}
|
||||||
return res.status(status.Ok);
|
return res.status(status.Ok);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+15
-14
@@ -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-Z0-9_ ]*$')).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);
|
||||||
|
|||||||
+21
-7
@@ -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,7 +41,7 @@ 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 }
|
||||||
@@ -52,8 +53,14 @@ module.exports = (users) => {
|
|||||||
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(
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
let rate = document.getElementsByClassName("countryButton").value;
|
|
||||||
|
|
||||||
document.getElementById("exchange").innerHTML
|
|
||||||
@@ -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";
|
||||||
|
|||||||
@@ -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';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -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",
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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');
|
||||||
|
|||||||
@@ -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';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
+57
-28
@@ -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,17 +476,17 @@ 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-Z0-9_ ]*$')).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,9 +633,9 @@ 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,9 +679,9 @@ 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;
|
||||||
|
|||||||
@@ -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,47 @@ 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 = asset.year;
|
||||||
|
const age = today.getFullYear() - assetYear;
|
||||||
|
let depreciatedValue;
|
||||||
|
|
||||||
|
if (age < 1){
|
||||||
|
depreciatedValue = asset.value * 0.9;
|
||||||
|
}else{
|
||||||
|
depreciatedValue = asset.value * Math.pow(0.90, 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 +104,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 +135,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 +171,4 @@ async function updateProgress(plans, assets, users, userId) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = { calculatePlanProgress, updatePlanProgressInDB, calculateProgress, updateProgress};
|
module.exports = { calculatePlanProgress, updatePlanProgressInDB, calculateTotalAssetValue, calculateProgress, updateProgress};
|
||||||
+11
-8
@@ -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
|
||||||
|
|||||||
+20
-4
@@ -5,17 +5,33 @@
|
|||||||
<!-- Buttons -->
|
<!-- Buttons -->
|
||||||
<div class="flex justify-end gap-4 px-5 py-6">
|
<div class="flex justify-end gap-4 px-5 py-6">
|
||||||
<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">
|
||||||
<% plans.forEach(plan=> { %>
|
<% if (plans.length === 0) { %>
|
||||||
|
<div class="col-span-1 col-start-2 ml-28 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>
|
||||||
|
|
||||||
|
<% } else %>
|
||||||
|
<% plans.forEach(plan=> { %>
|
||||||
<%- include('./partials/dashboardBox.ejs', {thing: plan}) %>
|
<%- include('./partials/dashboardBox.ejs', {thing: plan}) %>
|
||||||
<% }); %>
|
<% }); %>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
+20
-60
@@ -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') %>
|
||||||
|
|||||||
@@ -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
@@ -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,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>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -70,48 +81,127 @@
|
|||||||
<br>
|
<br>
|
||||||
|
|
||||||
<form id="<%= asset._id %>-form" method="POST" action="/updateAsset">
|
<form id="<%= asset._id %>-form" method="POST" action="/updateAsset">
|
||||||
<input id="type-<%= asset._id %>" name="type" type="text" hidden value="<%= asset.type %>">
|
<input id="type-<%= asset._id %>" name="type" type="text" hidden value="<%= asset.type %>">
|
||||||
<input id="id-<%= asset._id %>" name="id" type="text" hidden value="<%= asset._id %>">
|
<input id="id-<%= asset._id %>" name="id" type="text" hidden value="<%= asset._id %>">
|
||||||
<input id="userId-<%= asset.userId %>" name="userId" type="text" hidden value="<%= asset.userId %>">
|
<input id="userId-<%= asset.userId %>" name="userId" type="text" hidden value="<%= asset.userId %>">
|
||||||
<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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||||
|
|||||||
@@ -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
@@ -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>
|
||||||
|
|||||||
Reference in new issue
Block a user