Compare commits

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

No files matched your search

Vendored
BIN
View File
Binary file not shown.
+50 -24
View File
@@ -1,27 +1,43 @@
# Retirement Calculator
## 2800-202510-BBY14
### 2800-202510-BBY14
## Overview
Retirement Calculator - The goal of the application is to help users manage and track their financial progress in a way that reflects real life. Instead of just acting as a calculator, the app functions as a dynamic tracker. It shows the user a percentage of how much of their goal has been achieved and compares their actual performance to their planned targets.
RCalculator is a dynamic financial tracking application designed to help users manage and monitor their progress towards long-term financial goals, particularly retirement. It moves beyond simple calculations by providing personalized insights, tracking actual performance against planned targets, and offering a clear view of your financial journey towards achieving your desired future.
---
## Features
- Do calculator stuff
- **Personalized Financial Profile:** Users complete a comprehensive questionnaire to build a financial profile, enabling tailored guidance and projections.
- **Comprehensive Asset Management:** Add, track, and manage various types of assets including savings, stocks, real estate, and other investments for a holistic view of net worth.
- **Strategic Goal Planning:** Define long-term financial objectives (e.g., retirement, home purchase, education) and create detailed plans with actionable steps.
- **Clear Financial Projections:** Visualize potential investment growth and assess the feasibility of financial plans through easy-to-understand projections and charts.
- **User-Friendly Dashboard:** An intuitive dashboard provides an at-a-glance overview of financial health, asset allocation, and progress towards goals.
- **Secure User Authentication:** Robust authentication system to protect sensitive financial data, including secure registration and login processes.
- **Dynamic Currency Conversion:** (If applicable) Utilizes geolocation to provide relevant currency information and conversion rates for international users or assets.
---
## Technologies Used
Example:
- **Frontend**: HTML, CSS, JavaScript
- **Backend**: Express.js
- **Database**: MongoDB
- **Frontend:** EJS (Embedded JavaScript templates), Tailwind CSS, Vanilla JavaScript
- **Backend:** Node.js, Express.js
- **Database:** MongoDB
- **Authentication:** express-session for session management, bcrypt for password hashing.
- **Data Validation:** Joi for schema validation.
- **APIs:** Google Gemini API for generating financial advice. **Geolocation API** for currency conversion.
---
## Prerequisites
1. You'll need to setup an instance of MongoDB. You can use the cloud platform, MongoDB Atlas, or create your own instance locally.
i. [Create your MongoDB Altas instance.](https://www.mongodb.com/docs/manual/tutorial/getting-started/)
ii. [Create your local MongoDB server instance.](https://www.mongodb.com/docs/manual/administration/install-community/)
After this, you'll need to get your MongoURI from your database instance, as well as your database name for later configuration in the `.env` file.
## Usage
1. **Clone the repository:**
@@ -47,26 +63,30 @@ Example:
The application should now be running, typically on `http://localhost:3000` (or the port specified in your `.env` file).
---
## Project Structure
```
retirementCalculator/
├── src/
│ ├── views/
│ └── partials/
│ ├── css/
│ ├── images/
│ ├── scripts/
└── utils/
│ ├── auth/ # Authentication logic (e.g., passport setup, strategies)
├── database/ # MongoDB connection, schemas, models
│ ├── public/ # Static assets
├── images/ # Site images and icons
├── scripts/ # Client-side JavaScript files
│ └── svgs/ # SVG icons
│ ├── router/ # Express route definitions
│ ├── util/ # Utility functions (e.g., calculations, helpers)
│ └── views/ # EJS templates
│ └── partials/ # Reusable EJS partials (headers, footers, nav)
├── app.js
├── .env.example
├── .gitignore
├── LICENSE
├── package-lock.json
├── package.json
── README.md
├── .env.example # Example environment variables file
├── .gitignore # Specifies intentionally untracked files that Git should ignore
├── app.js # Main application entry point
├── CONTRIBUTING.md # Guidelines for contributing to the project
├── LICENSE # Project license information
├── package-lock.json # Records exact versions of dependencies
── package.json # Project metadata and dependencies
└── README.md # This file
```
---
@@ -78,16 +98,22 @@ retirementCalculator/
- **[Mitchell Schaeffer](https://github.com/knighthawk4227)** - BCIT CST Student with a passion for technology.
---
## more details to come...
## Limitations and Future Work
### Limitations
- None
- **Generalized Advice:** The application provides financial guidance based on user input and common models; it does not substitute professional financial advisory services.
- **Market Volatility:** Projections are based on assumed rates of return and do not account for all potential market fluctuations or unforeseen economic events.
- **Manual Data Entry:** Asset information and updates currently require manual input.
- **Single User Focus:** Primarily designed for individual financial planning.
### Future Work
- All work
- **Third-Party API Integrations:** Connect with financial institutions (e.g., Plaid) for automated asset tracking and transaction importing.
- **Advanced Reporting:** Implement more detailed financial reports, customizable charts, and data export options (e.g., PDF, CSV).
- **Enhanced Security Features:** Implement Two-Factor Authentication (2FA) and advanced security audits.
- **Educational Resources:** Integrate a section with articles, tips, and resources on financial literacy and planning.
---
+22 -2
View File
@@ -54,9 +54,21 @@ app.get('/', (req, res) => {
});
app.get('/signup', (req, res) => {
let error = req.session.errMessage;
delete req.session.errMessage;
const ignore = ["User not found", "Incorrect password"];
if (ignore.includes(req.session.errMessage)) req.session.errMessage = "";
res.render('signup', { errMessage: req.session.errMessage });
if (ignore.includes(error)) error = "";
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);
});
@@ -65,7 +77,15 @@ app.get('/login', (req, res) => {
res.redirect("/home");
return res.status(status.Ok);
}
if (req.query.email) {
res.render('login', {
errMessage: req.session.errMessage,
email: req.query.email
});
} else {
res.render('login', { errMessage: req.session.errMessage });
}
return res.status(status.Ok);
});
+15 -14
View File
@@ -27,14 +27,14 @@ module.exports = (users) => {
router.post("/login", async (req, res) => {
const credentialSchema = joi.object({
email: joi.string().email().required(),
password: joi.string().alphanum().max(20).required(),
email: joi.string().email({ minDomainSegments: 2, tlds: { allow: true } }).required(),
password: joi.string().max(20).required(),
});
const valid = credentialSchema.validate(req.body);
if (valid.err) {
req.session.errMessage = "Invalid input";
if (valid.error) {
req.session.errMessage = "Invalid input:" + valid.error.details.map(d => d.message.replace(/"/g, '')).join(', ');
res.status(status.BadRequest);
return res.redirect("/login");
}
@@ -43,13 +43,13 @@ module.exports = (users) => {
if (!user) {
req.session.errMessage = "User not found";
res.status(status.NotFound);
return res.redirect("/login");
return res.redirect(`/login/?email=${req.body.email}`);
}
if (!bcrypt.compareSync(req.body.password, user.password)) {
req.session.errMessage = "Incorrect password";
res.status(status.Unauthorized);
return res.redirect("/login");
return res.redirect(`/login/?email=${req.body.email}`);
}
req.session.authenticated = true;
@@ -72,16 +72,16 @@ module.exports = (users) => {
router.post("/signup", async (req, res) => {
const userSchema = joi.object({
email: joi.string().email().required(),
name: joi.string().pattern(new RegExp('^[a-zA-Z]+$')).max(20).required(),
password: joi.string().alphanum().max(20).min(8).required(),
repassword: joi.string().alphanum().max(20).min(8).required(),
email: joi.string().email({ minDomainSegments: 2, tlds: { allow: true } }).required(),
name: joi.string().pattern(new RegExp('^[a-zA-Z0-9_ ]*$')).max(20).required(),
password: joi.string().max(20).min(8).required(),
repassword: joi.string().max(20).min(8).required(),
});
const valid = userSchema.validate(req.body);
if (valid.err) {
req.session.errMessage = "Invalid input",
if (valid.error) {
req.session.errMessage = valid.error.details[0].message,
res.status(status.BadRequest);
return res.redirect("/signup");
}
@@ -95,8 +95,9 @@ module.exports = (users) => {
if (req.body.password != req.body.repassword) {
req.session.errMessage = "Passwords must match";
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);
@@ -104,7 +105,7 @@ module.exports = (users) => {
if (strength.id < 2) {
req.session.errMessage = `Password ${strength.value}`;
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);
+22 -8
View File
@@ -1,8 +1,9 @@
const { passwordStrength } = require("check-password-strength");
const nodeMail = require('nodemailer');
const express = require('express');
const bcrypt = require('bcrypt');
const crypto = require('crypto');
const joi = require('joi');
const nodeMail = require('nodemailer');
const bcrypt = require('bcrypt');
require('dotenv').config();
const PORT = process.env.PORT;
@@ -20,7 +21,7 @@ module.exports = (users) => {
router.post('/auth/resetPass', async (req, res) => {
const resetSchema = joi.object({
email: joi.string().email().required(),
email: joi.string().email({ minDomainSegments: 2, tlds: { allow: true } }).required(),
});
req.session.error = '';
req.session.reset = '';
@@ -40,20 +41,26 @@ module.exports = (users) => {
const token = crypto.randomBytes(32).toString('hex');
console.log(`The reset token is ${token}`)
const expiration = Date.now() + 360000;
const expiration = Date.now() + 3600000;
await users.updateOne({ email }, {
$set: { resetToken: token, resetTokenExpires: expiration }
});
const resetUrl = `http://localhost:${PORT}/reset/${token}`;
const resetUrl = `https://rcalculator.sowinski.dev/reset/${token}`;
const mailSend = {
from: process.env.EMAIL_USER,
to: email,
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 {
@@ -76,7 +83,7 @@ module.exports = (users) => {
const valid = passwordSchema.validate({ password, confirmPassword });
if (valid.error) {
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);
return res.redirect(`/reset/${token}`);
}
@@ -97,6 +104,13 @@ module.exports = (users) => {
req.session.error = 'Reset link is invalid.';
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);
await users.updateOne(
+65
View File
@@ -85,6 +85,8 @@ function lockAsset(assetId, icon) {
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}`).classList.remove("cursor-pointer");
document.getElementById(`save-${assetId}`).classList.add("cursor-not-allowed");
@@ -105,6 +107,8 @@ function unlockAsset(assetId, icon) {
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}`).classList.remove("cursor-not-allowed");
document.getElementById(`save-${assetId}`).classList.add("cursor-pointer");
@@ -133,6 +137,8 @@ function autoOpenCreate() {
* @param {string} assetId
*/
function selectIcon(selectedIcon, assetId="") {
toggleYear(selectedIcon.value, assetId);
document.getElementById(`dropdown-icon-button${assetId != "" ? "-" : ""}${assetId}`).value = selectedIcon.value;
document.getElementById(`icon${assetId != "" ? "-" : ""}${assetId}`).value = selectedIcon.value
@@ -144,5 +150,64 @@ function selectIcon(selectedIcon, assetId="") {
`;
}
/**
* selectFilter to filter assets by type
* @param {button elemenbt} filter
*/
function selectFilter(filter) {
const types = ["other", "saving", "stock"];
console.log(filter.value);
types.forEach((type) => {
if (filter.value == "all") {
document.querySelectorAll(`.${type}-asset`).forEach((preview) => {
preview.style.display = "block";
});
} else if (type != filter.value) {
document.querySelectorAll(`.${type}-asset`).forEach((preview) => {
preview.style.display = "none";
});
} else if (type == filter.value) {
document.querySelectorAll(`.${type}-asset`).forEach((preview) => {
preview.style.display = "block";
});
}
});
document.getElementById("dropdown-filters-button").innerHTML = `
<span class="inline-flex items-center">
${filter.value.charAt(0).toUpperCase() + filter.value.slice(1)} Assets
<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"/>
</svg>
</span>
`;
}
/**
* 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();
autoOpenCreate();
-3
View File
@@ -1,3 +0,0 @@
let rate = document.getElementsByClassName("countryButton").value;
document.getElementById("exchange").innerHTML
+4 -4
View File
@@ -1,12 +1,12 @@
/**
* Function takes in a number and formats it to currency
* @param {integer}
* @returns nuber formatted in currency
* @param {number} value
* @returns {string} number formatted in currency
*/
document.addEventListener("DOMContentLoaded", () => {
const number = document.querySelectorAll(".planGoal");
number.forEach(value => {
number.forEach((value) => {
num = parseFloat(value.textContent);
if (num >= 999999) {
value.textContent = "$" + (num /= 1000000) + "M";
+67
View File
@@ -0,0 +1,67 @@
// Basic client-side form submission handler to process JSON response
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('editPlanForm');
const errorMessagesDiv = document.getElementById('formErrorMessages');
// Store initial form values
const initialValues = {
name: form.name.value,
retirementAge: form.retirementAge.value,
retirementExpenses: form.retirementExpenses.value,
retirementAssets: form.retirementAssets.value,
retirementLiabilities: form.retirementLiabilities.value
};
const resetButton = document.getElementById('resetButton');
resetButton.addEventListener('click', function() {
form.name.value = initialValues.name;
form.retirementAge.value = initialValues.retirementAge;
form.retirementExpenses.value = initialValues.retirementExpenses;
form.retirementAssets.value = initialValues.retirementAssets;
form.retirementLiabilities.value = initialValues.retirementLiabilities;
if (errorMessagesDiv) {
errorMessagesDiv.style.display = 'none'; // Hide error message on reset
errorMessagesDiv.textContent = '';
}
});
if (form) {
form.addEventListener('submit', async function(event) {
event.preventDefault(); // Prevent traditional form submission
errorMessagesDiv.textContent = '';
errorMessagesDiv.style.display = 'none';
const formData = new FormData(form);
const data = {};
formData.forEach((value, key) => {
data[key] = value;
});
try {
const response = await fetch(form.action, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
const result = await response.json();
if (response.ok && result.success) {
const actionUrl = form.action;
const urlParts = actionUrl.split('/');
const planId = urlParts[urlParts.length - 2];
window.location.href = `/plans/${planId}`;
} else {
errorMessagesDiv.textContent = result.message || 'An error occurred while updating the plan.';
errorMessagesDiv.style.display = 'block';
}
} catch (error) {
console.error('Error submitting form:', error);
errorMessagesDiv.textContent = 'A network error occurred. Please try again.';
errorMessagesDiv.style.display = 'block';
}
});
}
});
+2 -1
View File
@@ -6,13 +6,14 @@ const factSubmitButton = document.getElementById("factSubmitButton");
var path = window.location.pathname;
var page = path.split("/").pop();
if(page !== "login" && page !== "signup" && page !== "forgotPassword" && page !== "") {
if(page !== "login" && page !== "signup" && page !== "forgotPassword" && page !== "aboutUs" && page !== "") {
factButton.classList.remove("hidden");
}
factButton.addEventListener("click", () => {
factMenu.classList.toggle("hidden");
});
factSubmitButton.addEventListener("click", () => {
fetch("/fact", {
method: "POST",
+24
View File
@@ -1,9 +1,17 @@
/**
* getLocation gets the geolocation of user on page load.
*/
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(getLatestExchange, error);
}
}
/**
* update the currency exchange rate dropdown
* or display error
* @param {object} data
*/
function update(data) {
if (data.data.message != "error") {
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) {
document.getElementById("dropdown-country-button").value = clickedButton.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) {
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) {
let lat = position.coords.latitude;
let lon = position.coords.longitude;
@@ -73,6 +93,10 @@ async function getLatestExchange(position) {
update(data);
}
/**
* error displays error message
* @param {string} err
*/
function error(err) {
const data = {
data: {
+8
View File
@@ -1,3 +1,7 @@
/**
* editPlan redirect
* @param {string} planId
*/
function editPlan(planId) {
if (planId) {
window.location.href = `/plans/${planId}/edit`;
@@ -7,6 +11,10 @@ function editPlan(planId) {
}
}
/**
* deletePlan handler
* @param {string} planId
*/
async function deletePlan(planId) {
if (!planId) {
console.error('deletePlan called without a planId');
+85
View File
@@ -0,0 +1,85 @@
document.addEventListener('DOMContentLoaded', () => {
const openModalBtn = document.getElementById('openNewPlanModalBtn');
const newPlanDialog = document.getElementById('newPlanDialog');
const newPlanForm = document.getElementById('newPlanForm');
const submitNewPlanBtn = document.getElementById('submitNewPlanBtn');
const newPlanError = document.getElementById('newPlanError');
const resetNewPlanFormBtn = document.getElementById('resetNewPlanFormBtn');
let url = new URLSearchParams(window.location.search);
let modalValue = url.get('openModal');
if (modalValue === 'true') {
newPlanDialog.showModal();
}
openModalBtn.addEventListener('click', () => {
newPlanDialog.showModal();
// Error reset is now handled by 'close' event, but good to clear on open too
newPlanError.style.display = 'none';
newPlanError.textContent = '';
});
// Handles form reset and error clearing when dialog is closed by any means (Esc, Cancel button, backdrop click, successful submit)
newPlanDialog.addEventListener('close', () => {
newPlanForm.reset();
newPlanError.style.display = 'none';
newPlanError.textContent = '';
});
// Close dialog if user clicks on the backdrop
newPlanDialog.addEventListener('click', (event) => {
if (event.target === newPlanDialog) {
newPlanDialog.close(); // This will trigger the 'close' event listener above
}
});
resetNewPlanFormBtn.addEventListener('click', () => {
newPlanForm.reset();
newPlanError.style.display = 'none';
newPlanError.textContent = '';
});
newPlanForm.addEventListener('submit', async (event) => {
event.preventDefault();
submitNewPlanBtn.disabled = true;
submitNewPlanBtn.textContent = 'Saving...';
newPlanError.style.display = 'none';
const formData = new FormData(newPlanForm);
const data = Object.fromEntries(formData.entries());
try {
const response = await fetch('/newPlan', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (response.ok) {
const result = await response.json();
if (result.success) {
newPlanDialog.close(); // This will trigger the 'close' event listener
window.location.reload(); // Reload to see the new plan
} else {
newPlanError.textContent = result.message || 'Failed to create plan. Please try again.';
newPlanError.style.display = 'block';
}
} else {
const errorData = await response.json();
newPlanError.textContent = errorData.message || `Error: ${response.status} - ${response.statusText}`;
newPlanError.style.display = 'block';
}
} catch (error) {
console.error('Error submitting new plan:', error);
newPlanError.textContent = 'An unexpected error occurred. Please try again.';
newPlanError.style.display = 'block';
}
finally {
submitNewPlanBtn.disabled = false;
submitNewPlanBtn.textContent = 'Save Plan';
}
});
});
+1 -1
View File
@@ -1,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="white" viewBox="0 0 24 24" aria-hidden="true">
<path d="M10.464 8.746c.227-.18.497-.311.786-.394v2.795a2.252 2.252 0 0 1-.786-.393c-.394-.313-.546-.681-.546-1.004 0-.323.152-.691.546-1.004ZM12.75 15.662v-2.824c.347.085.664.228.921.421.427.32.579.686.579.991 0 .305-.152.671-.579.991a2.534 2.534 0 0 1-.921.42Z" />
<path fill-rule="evenodd" d="M12 2.25c-5.385 0-9.75 4.365-9.75 9.75s4.365 9.75 9.75 9.75 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25ZM12.75 6a.75.75 0 0 0-1.5 0v.816a3.836 3.836 0 0 0-1.72.756c-.712.566-1.112 1.35-1.112 2.178 0 .829.4 1.612 1.113 2.178.502.4 1.102.647 1.719.756v2.978a2.536 2.536 0 0 1-.921-.421l-.879-.66a.75.75 0 0 0-.9 1.2l.879.66c.533.4 1.169.645 1.821.75V18a.75.75 0 0 0 1.5 0v-.81a4.124 4.124 0 0 0 1.821-.749c.745-.559 1.179-1.344 1.179-2.191 0-.847-.434-1.632-1.179-2.191a4.122 4.122 0 0 0-1.821-.75V8.354c.29.082.559.213.786.393l.415.33a.75.75 0 0 0 .933-1.175l-.415-.33a3.836 3.836 0 0 0-1.719-.755V6Z" clip-rule="evenodd" />
</svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

+1 -1
View File
@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="currentColor" viewBox="0 0 24 24">
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="white" viewBox="0 0 24 24">
<path fill-rule="evenodd" d="M11.293 3.293a1 1 0 0 1 1.414 0l6 6 2 2a1 1 0 0 1-1.414 1.414L19 12.414V19a2 2 0 0 1-2 2h-3a1 1 0 0 1-1-1v-3h-2v3a1 1 0 0 1-1 1H7a2 2 0 0 1-2-2v-6.586l-.293.293a1 1 0 0 1-1.414-1.414l2-2 6-6Z" clip-rule="evenodd"/>
</svg>

Before

Width:  |  Height:  |  Size: 358 B

After

Width:  |  Height:  |  Size: 351 B

+2 -2
View File
@@ -1,3 +1,3 @@
<svg height="30" width="30" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 17 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15"></path>
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="white" viewBox="0 0 24 24">
<path stroke="white" stroke-linecap="round" stroke-width="2" d="M5 6h14 M5 14h14 M5 22h14"/>
</svg>

Before

Width:  |  Height:  |  Size: 260 B

After

Width:  |  Height:  |  Size: 199 B

+1 -1
View File
@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="currentColor" viewBox="0 0 24 24">
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="30" fill="white" viewBox="0 0 24 24">
<path d="M13.09 3.294c1.924.95 3.422 1.69 5.472.692a1 1 0 0 1 1.438.9v9.54a1 1 0 0 1-.562.9c-2.981 1.45-5.382.24-7.25-.701a38.739 38.739 0 0 0-.622-.31c-1.033-.497-1.887-.812-2.756-.77-.76.036-1.672.357-2.81 1.396V21a1 1 0 1 1-2 0V4.971a1 1 0 0 1 .297-.71c1.522-1.506 2.967-2.185 4.417-2.255 1.407-.068 2.653.453 3.72.967.225.108.443.216.655.32Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 463 B

After

Width:  |  Height:  |  Size: 456 B

+43 -26
View File
@@ -1,5 +1,5 @@
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 status = require("../util/statuses");
const ObjectId = require('mongodb').ObjectId;
@@ -24,29 +24,33 @@ const getAssetSchema = (type) => {
assetSchema = joi.object({
type: joi.string().valid("other", "stock", "saving").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(),
year: joi.number().min(1900).max(new Date().getFullYear()),
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
userId: joi.string().alphanum(),
});
break;
case "saving":
assetSchema = joi.object({
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(),
id: joi.string().alphanum(), // May be passed when updating existing asset
userId: joi.string().alphanum(),
});
break;
case "stock":
assetSchema = joi.object({
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(),
quantity: joi.number().min(1).required(),
purchaseDate: joi.date().required(),
id: joi.string().alphanum(), // May be passed when updating existing asset
userId: joi.string().alphanum(),
});
break;
default:
@@ -105,18 +109,33 @@ module.exports = (middleware, users, plans, assets) => {
});
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 totalUserAssetValue = await calculateTotalAssetValue(userAssets);
res.render('assets', {
user: req.session.user,
errMessage: req.session.errMessage,
assets: userAssets,
geoData: req.session.geoData,
totalUserAssetValue: totalUserAssetValue,
icons: icons,
});
return res.status(status.Ok);
});
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 {
const updatedUserPlans = await updateProgress(plans, assets, users, req.session.user._id);
@@ -138,6 +157,7 @@ module.exports = (middleware, users, plans, assets) => {
try {
const planId = req.params.id;
let userAssets = await assets.find({ userId: new ObjectId(req.session.userId) }).toArray();
let totalUserAssetValue = await calculateTotalAssetValue(userAssets);
if (!ObjectId.isValid(planId)) {
req.session.errMessage = "Invalid plan ID format.";
@@ -152,13 +172,16 @@ module.exports = (middleware, users, plans, assets) => {
const progress = await calculateProgress(plan, assets, users, req.session.userId);
const suggestions = await generateSuggestions(totalUserAssetValue, plan);
res.render('planDetail', {
user: req.session.user,
plan: plan,
geoData: req.session.geoData,
totalUserAssetValue: totalUserAssetValue,
assets: userAssets,
progress: progress,
suggestions: await suggestions.generateSuggestions(),
suggestions: suggestions,
});
} catch (err) {
@@ -168,21 +191,13 @@ module.exports = (middleware, users, plans, assets) => {
}
});
router.get('/newPlan', (req, res) => {
router.post('/newPlan', 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');
}
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({
name: joi.string().min(3).max(100).required(),
retirementAge: joi.number().min(18).max(120).required(),
@@ -449,16 +464,16 @@ module.exports = (middleware, users, plans, assets) => {
router.post("/updateAccount", async (req, res) => {
const accountSchema = joi.object({
email: joi.string().email(),
name: joi.string().pattern(new RegExp('^[a-zA-Z]+$')).max(20),
password: joi.string().alphanum().max(20).min(8),
repassword: joi.string().alphanum().max(20).min(8),
email: joi.string().email({ minDomainSegments: 2, tlds: { allow: true } }),
name: joi.string().pattern(new RegExp('^[a-zA-Z0-9_ ]*$')).max(20),
password: joi.string().max(20).min(8),
repassword: joi.string().max(20).min(8),
});
const valid = accountSchema.validate(req.body);
if (valid.err) {
req.session.errMessage = "Invalid input",
if (valid.error) {
req.session.errMessage = "Invalid input:" + valid.error.details.map(d => d.message.replace(/"/g, '')).join(', ');
res.status(status.BadRequest);
return res.redirect("/profile");
}
@@ -606,8 +621,8 @@ module.exports = (middleware, users, plans, assets) => {
const valid = assetSchema.validate(req.body);
if (valid.err) {
req.session.errMessage = "Invalid input",
if (valid.error) {
req.session.errMessage = "Invalid input:" + valid.error.details.map(d => d.message.replace(/"/g, '')).join(', ');
res.status(status.BadRequest);
return res.redirect("/assets");
}
@@ -624,6 +639,7 @@ module.exports = (middleware, users, plans, assets) => {
newAsset.value = newAsset.quantity * newAsset.price;
newAsset.name = `${newAsset.ticker} Stock`;
}
if (type == "other" && newAsset.year != "") newAsset.year = parseInt(newAsset.year);
newAsset.value = parseFloat(newAsset.value);
newAsset.icon = type == "stock" ? "Stock" : type == "saving" ? "Coins" : newAsset.icon;
@@ -651,8 +667,8 @@ module.exports = (middleware, users, plans, assets) => {
const valid = assetSchema.validate(req.body);
if (valid.err) {
req.session.errMessage = "Invalid input",
if (valid.error) {
req.session.errMessage = "Invalid input:" + valid.error.details.map(d => d.message.replace(/"/g, '')).join(', ');
res.status(status.BadRequest);
return res.redirect("/assets");
}
@@ -670,6 +686,7 @@ module.exports = (middleware, users, plans, assets) => {
update.value = update.quantity * update.price;
update.name = `${update.ticker} Stock`;
}
if (type == "other" && update.year != "") update.year = parseInt(update.year);
update.value = parseFloat(update.value);
delete update.id;
delete update.userId;
+49 -3
View File
@@ -12,7 +12,7 @@ async function calculatePlanProgress(plans, assets, userId) {
try {
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;
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) {
if (!plan || typeof plan !== 'object') {
@@ -63,9 +104,10 @@ async function calculateProgress(plan, assets, users, userId) {
try {
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 userDoc = await users.findOne({ _id: new ObjectId(userId) });
if (!userDoc || !userDoc.dob) {
@@ -93,6 +135,10 @@ async function calculateProgress(plan, assets, users, userId) {
const percentageCalculated = (totalUserAssetValue / (totalUserPlanValue + totalCostOfRetirement)) * 100;
if (monthsUntilRetirement <= 0) {
return { monthlyInvestment: 0, totalCostOfRetirement: totalCostOfRetirement, monthsUntilRetirement: 0, yearsRetired: yearsRetired, yearsUntilRetirement: 0, percentage: 0 };
}
const progress = {};
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};
+6 -2
View File
@@ -10,10 +10,14 @@ async function generateFact(factInput) {
return response.text;
}
async function generateSuggestions() {
async function generateSuggestions(totalUserAssetValue, plan) {
const response = await ai.models.generateContent({
model: "gemini-2.0-flash",
contents: "Return a mock investment suggestion 3-5 lines only",
contents: `Return an investment suggestion 3-5 based on the user's total asset value and their plan. Please do not include any extra text or explanation.
Total User Asset Value: ${totalUserAssetValue}
Plan: ${plan}`,
});
return response.text;
}
+48 -16
View File
@@ -10,25 +10,57 @@
<section class="bg-gray-900 py-12 md:py-20">
<div class="max-w-screen-lg mx-auto px-4 text-center">
<h1 class="mb-4 text-4xl font-extrabold tracking-tight leading-none md:text-5xl lg:text-6xl text-white">About RCalculator</h1>
<p class="mb-12 text-lg font-normal lg:text-xl sm:px-16 text-gray-400">Welcome to RCalculator, your partner in building a secure and fulfilling financial future.</p>
<p class="mb-12 text-lg font-normal lg:text-xl sm:px-16 text-gray-400">Welcome to RCalculator, your partner in navigating the path to a secure and fulfilling financial future. We simplify financial planning, making it accessible and actionable for everyone.</p>
</div>
<div class="max-w-screen-md mx-auto px-4">
<h2 class="mb-4 text-3xl font-bold tracking-tight text-white text-center">Our Mission</h2>
<p class="mb-6 font-normal text-gray-400 text-lg text-center">
Our mission is to empower you with the tools and insights needed to take control of your long-term financial goals.
</p>
<div class="prose lg:prose-lg mx-auto text-gray-400">
<p class="mb-4">
We believe that planning for your desired future, especially retirement, shouldn't be daunting. It doesn't matter what your current age or career status is starting now is what counts.
</p>
<p class="mb-4">
At RCalculator, we help you trace a personalized plan that suits your unique aspirations. We provide clear, actionable advice and projections to guide you on how to get there, whether you're just starting your career, mid-way through, or nearing retirement.
</p>
<p>
Let us help you navigate the path to financial independence and achieve the retirement lifestyle you envision.
</p>
<!-- What RCalculator Offers Section -->
<div class="max-w-screen-md mx-auto px-4 mb-16">
<h2 class="mb-8 text-3xl font-bold tracking-tight text-white text-center">What RCalculator Offers</h2>
<div class="space-y-8 text-gray-400 text-lg">
<div class="p-6 border border-gray-700 rounded-lg bg-gray-800/30 shadow-lg">
<h3 class="font-semibold text-xl text-white mb-2">Personalized Financial Insights</h3>
<p>Start by completing our comprehensive questionnaire. Your answers help us tailor financial guidance and projections specifically to your situation and goals.</p>
</div>
<div class="p-6 border border-gray-700 rounded-lg bg-gray-800/30 shadow-lg">
<h3 class="font-semibold text-xl text-white mb-2">Comprehensive Asset Tracking</h3>
<p>Easily add and monitor various types of assets, from savings accounts and stocks to real estate and other investments. Get a clear overview of your net worth in one place.</p>
</div>
<div class="p-6 border border-gray-700 rounded-lg bg-gray-800/30 shadow-lg">
<h3 class="font-semibold text-xl text-white mb-2">Strategic Goal Planning</h3>
<p>Define your long-term financial objectives, such as retirement, buying a home, or funding education. Create detailed plans and see how you can achieve them.</p>
</div>
<div class="p-6 border border-gray-700 rounded-lg bg-gray-800/30 shadow-lg">
<h3 class="font-semibold text-xl text-white mb-2">Clear Financial Projections</h3>
<p>Understand the potential growth of your investments and the feasibility of your financial plans with our easy-to-understand projections and visual tools.</p>
</div>
</div>
</div>
<!-- Getting Started Section -->
<div class="max-w-screen-md mx-auto px-4 mb-16">
<h2 class="mb-8 text-3xl font-bold tracking-tight text-white text-center">Getting Started with RCalculator</h2>
<ol class="list-decimal list-inside space-y-6 text-gray-400 text-lg">
<li class="p-4 border border-gray-700 rounded-lg bg-gray-800 shadow-md">
<span class="font-semibold text-white">Sign Up & Build Your Profile:</span> Create your account and fill out our initial questionnaire. This provides the foundation for personalized advice.
</li>
<li class="p-4 border border-gray-700 rounded-lg bg-gray-800 shadow-md">
<span class="font-semibold text-white">Log Your Assets:</span> Navigate to the 'Assets' section to add details about your current savings, investments, and other valuable possessions.
</li>
<li class="p-4 border border-gray-700 rounded-lg bg-gray-800 shadow-md">
<span class="font-semibold text-white">Create Financial Plans:</span> Head to the 'Plans' section to outline your financial goals. Set targets, timelines, and see projected outcomes.
</li>
<li class="p-4 border border-gray-700 rounded-lg bg-gray-800 shadow-md">
<span class="font-semibold text-white">Monitor & Adjust:</span> Regularly review your dashboard to track your progress. Adjust your plans and asset information as your circumstances change.
</li>
</ol>
</div>
<!-- Our Commitment Section -->
<div class="max-w-screen-md mx-auto px-4">
<h2 class="mb-6 text-3xl font-bold tracking-tight text-white text-center">Our Commitment</h2>
<p class="text-gray-400 text-lg text-center">
Our mission is to empower you with clarity and confidence in your financial decisions. We believe that with the right tools and guidance, anyone can achieve their financial aspirations. Let RCalculator be your guide to financial independence.
</p>
</div>
<% if (!geoData) { %>
+51 -12
View File
@@ -10,26 +10,65 @@
<!-- Main display -->
<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="my-2 mx-4 md:max-w-md 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">
<h1 class="text-2xl font-bold tracking-tight leading-none">Assets:</h1>
<h1 class="text-xl font-medium tracking-tight leading-none"><%= assets.length %></h1>
<h1 class="text-lg font-medium">Assets:</h1>
<h1 class="text-lg font-medium"><%= assets.length %></h1>
</div>
<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-xl font-medium tracking-tight leading-none">
<h1 class="text-lg font-medium">Total Value:</h1>
<h1 class="text-lg font-medium">
<!-- 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>
</div>
</div>
<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">
Back to Dashboard
</a>
<div class="my-6 md:w-md md:mx-auto px-4 flex flex-row justify-between">
<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"
id="dropdown-filters-button"
value=""
data-dropdown-toggle="dropdown-filters"
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 cursor-pointer"
type="button"
>
<span class="inline-flex items-center">
All Assets
<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"/>
</svg>
</span>
</button>
<div id="dropdown-filters" class="overflow-y-auto h-40 z-10 hidden divide-y divide-gray-100 rounded-lg shadow-sm w-32 bg-gray-700">
<ul id="dropdown-filters-ul" class="py-2 text-sm text-gray-200" aria-labelledby="dropdown-filters-button">
<li>
<button onclick="selectFilter(this)" type="button" value="all" 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">All Assets</span>
</button>
</li>
<li>
<button onclick="selectFilter(this)" type="button" value="other" 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">Other Assets</span>
</button>
</li>
<li>
<button onclick="selectFilter(this)" type="button" value="saving" 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">Savings Assets</span>
</button>
</li>
<li>
<button onclick="selectFilter(this)" type="button" value="stock" 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">Stocks Assets</span>
</button>
</li>
</ul>
</div>
<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-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()"
>
Add New Asset
@@ -40,7 +79,7 @@
<hr class="h-px my-8 bg-gray-500 border-0 mx-10">
<!-- list each asset and create hidden popup modal for view/edit -->
<section class="mt-6">
<section class="my-6 mx-4">
<% if (assets.length == 0) { %>
<div class="max-w-md mx-auto mb-10 rounded-lg shadow-md text-white font-bold bg-red-400 py-4 text-center">
You have no assets, create a new asset.
+21 -3
View File
@@ -3,19 +3,37 @@
<main class="pt-24 pb-14">
<!-- Buttons -->
<div class="flex justify-end gap-4 px-5 py-6">
<div class="flex md:justify-end justify-center gap-4 px-5 py-6 md:py-0">
<a href="/assets?popup"
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>
<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"
type="button">Create new plan </a>
</div>
<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">
<% if (plans.length === 0) { %>
<div class="flex justify-center md:col-start-2">
<div class="col-span-1 mt-25">
<div class="w-86 p-4 bg-white rounded-lg shadow-xl border border-slate-200">
<h1 class="text-2xl block text-center font-semibold text-slate-700">Don't have any retirement plans? </h1>
<div class="mt-4 flex justify-center items-center">
<div class="mt-4 flex justify-center">
<a href="/plans" class="cursor-pointer border-2 border-blue-600 bg-blue-600 text-white py-2 px-6
rounded-md hover:bg-blue-700 hover:border-blue-700 font-semibold
transition-colors duration-200 focus:outline-none ">Make one</a>
</div>
</div>
</div>
</div>
</div>
<% } else %>
<% plans.forEach(plan=> { %>
<%- include('./partials/dashboardBox.ejs', {thing: plan}) %>
<% }); %>
</div>
</div>
</main>
+20 -60
View File
@@ -4,98 +4,58 @@
<main class="container mx-auto p-4 pt-28">
<div class="max-w-2xl mx-auto bg-gray-800 p-6 sm:p-8 rounded-xl shadow-xl">
<h2 class="text-2xl font-bold text-center text-white mb-8">Edit Plan: <%= plan.name %></h2>
<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">
<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
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>
<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"
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>
<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"
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>
<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"
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>
<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"
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 id="formErrorMessages" class="text-red-400 text-sm my-2" style="display: none;"></div>
<div class="flex items-center justify-end space-x-4 pt-4">
<a href="/plans/<%= plan._id %>" class="inline-flex justify-center py-2 px-4 border border-gray-600 rounded-md shadow-sm bg-gray-600 text-sm font-medium text-white hover:bg-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-indigo-500">
Cancel
</a>
<div class="flex items-center justify-center space-x-4 pt-4">
<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
</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>
</form>
</div>
</main>
<script>
// Basic client-side form submission handler to process JSON response
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('editPlanForm');
const errorMessagesDiv = document.getElementById('formErrorMessages');
if (form) {
form.addEventListener('submit', async function(event) {
event.preventDefault(); // Prevent traditional form submission
errorMessagesDiv.textContent = '';
errorMessagesDiv.style.display = 'none';
const formData = new FormData(form);
const data = {};
formData.forEach((value, key) => {
data[key] = value;
});
try {
const response = await fetch(form.action, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
const result = await response.json();
if (response.ok && result.success) {
// alert('Plan updated successfully!'); // Optional: show an alert
window.location.href = `/plans/<%= plan._id %>`; // Redirect to plan detail page
} else {
errorMessagesDiv.textContent = result.message || 'An error occurred while updating the plan.';
errorMessagesDiv.style.display = 'block';
}
} catch (error) {
console.error('Error submitting form:', error);
errorMessagesDiv.textContent = 'A network error occurred. Please try again.';
errorMessagesDiv.style.display = 'block';
}
});
}
});
</script>
<script src="/static/scripts/editPlanModals.js"></script>
<%- include('./partials/navBar') %>
<%- include('./partials/scriptLoader') %>
+3 -3
View File
@@ -1,7 +1,7 @@
<%- include("./partials/fileHeader") %>
<%- 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">
<form action="/auth/resetPass" method="POST">
<div class="w-96 p-8 bg-white rounded-lg shadow-xl border border-slate-200">
@@ -12,7 +12,7 @@
<div class="mt-4">
<label for="email" class="block text-base mb-2 text-slate-600">Email</label>
<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" />
</div>
<% } else { %>
@@ -31,7 +31,7 @@
<% if (!reset) { %>
<div class="mt-6">
<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>
+13 -12
View File
@@ -6,17 +6,18 @@
<form action="/login" method="POST">
<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>
<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">
<label for="email" class="block text-base mb-2 text-slate-600">Email</label>
<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"
placeholder="Enter Email" />
<label for="email" class="block text-base mb-2 text-slate-600">Email <span class="text-red-500">*</span></label>
<input required type="text" id="email" name="email"
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" value="<%= typeof email != 'undefined' ? email : '' %>"/>
</div>
<div class="mt-4">
<label for="password" class="block text-base mb-2 text-slate-600">Password</label>
<input 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"
<label for="password" class="block text-base mb-2 text-slate-600">Password <span class="text-red-500">*</span></label>
<input required type="password" id="password" name="password"
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" />
</div>
<% if (errMessage && errMessage.length > 0) { %>
@@ -26,20 +27,20 @@
<% } %>
<div class="mt-4 flex justify-between 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>
</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 class="mt-6">
<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 class="mt-6 text-center">
<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>
</form>
-10
View File
@@ -15,16 +15,6 @@
</svg>
</div>
</a>
<a href="/settings" class="relative flex flex-col bg-clip-border rounded-xl bg-white text-gray-700 shadow-md">
<div class="p-4 flex items-center justify-between">
<p class="font-sans text-2xl leading-normal font-normal text-blue-gray-600">
Settings
</p>
<svg class="w-[40px] h-[40px]" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="m10 16 4-4-4-4"/>
</svg>
</div>
</a>
<a href="/aboutUs" class="relative flex flex-col bg-clip-border rounded-xl bg-white text-gray-700 shadow-md">
<div class="p-4 flex items-center justify-between">
<p class="font-sans text-2xl leading-normal font-normal text-blue-gray-600">
+4 -1
View File
@@ -4,7 +4,10 @@
<!-- Modal content -->
<div class="relative p-4 text-center rounded-lg shadow bg-gray-800 sm:p-5">
<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>
<span class="sr-only">Close modal</span>
</button>
+110 -20
View File
@@ -8,7 +8,8 @@
style="width: 100%;"
onclick="lockAsset('<%= asset._id %>', '<%= asset.icon %>')"
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
</button>
@@ -30,7 +31,13 @@
<div class="flex flex-row justify-between">
<% if (asset.type == "other") { %>
<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 %>
<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"/>
@@ -51,7 +58,11 @@
</div>
</div>
<% } 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 %>">
</button>
<% } %>
@@ -61,7 +72,7 @@
<button
id="edit-<%= 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
</button>
@@ -76,42 +87,121 @@
<input id="icon-<%= asset._id %>" name="icon" type="text" hidden value="<%= asset.icon %>">
<% if (asset.type != "stock") { %>
<label for="name" class="block text-sm font-medium text-gray-700 mt-6">Name</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">
<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-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 { %>
<label for="ticker" class="block text-sm font-medium text-gray-700 mt-2">Stock Ticker</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">
<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-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">
<% if (asset.type != "stock") { %>
<label for="value" class="block text-sm font-medium text-gray-700">Value</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">
<% } else { %>
<label for="price" class="block text-sm font-medium text-gray-700 mt-2">Price per Share</label>
<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">
<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"
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>
<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">
<div
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") { %>
<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") { %>
<label for="purchaseDate" class="block text-sm font-medium text-gray-700 mt-2">Purchased on</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">
<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-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>
<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>
<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
</button>
</div>
+7 -3
View File
@@ -1,6 +1,10 @@
<!-- Clickable asset list item -->
<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">
<div class="mt-4 max-w-md mx-auto bg-white rounded-lg shadow-md <%= asset.type %>-asset">
<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="mx-2">
@@ -10,7 +14,7 @@
</div>
<div>
<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>
</div>
</div>
+157 -31
View File
@@ -6,26 +6,49 @@
<button
onclick="resetAll()"
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
</button>
</form>
</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">
<label class="block text-sm font-medium text-gray-700">Asset Type</label>
<div class="mt-1 space-x-4">
<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>
</label>
<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>
</label>
<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>
</label>
</div>
@@ -38,7 +61,12 @@
<label for="name" class="block text-sm font-medium text-gray-700 mt-2">Select an Icon</label>
<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
<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"/>
@@ -48,7 +76,13 @@
<ul id="dropdown" class="py-2 text-sm text-gray-200" aria-labelledby="dropdown-icon-button">
<% icons.forEach(icon => { %>
<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">
<img src="/static/svgs/icons/<%= icon %>.svg" class="h-4 w-4 me-2" alt="<%= icon %>"> (<%= icon %>)
</span>
@@ -59,29 +93,74 @@
</div>
</div>
<label for="name" class="block text-sm font-medium text-gray-700 mt-2">Asset Name</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">
<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-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>
<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">
<div style="display: none;" id="year-create">
<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>
<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">
<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
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>
<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">
<button
type="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
</button>
<button
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
</button>
@@ -92,23 +171,40 @@
<form style="display: none;" method="POST" action="/createAsset" id="create-saving-asset-form">
<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>
<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">
<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-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>
<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="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"
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">
<button
type="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
</button>
<button
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
</button>
@@ -119,29 +215,59 @@
<form style="display: none;" method="POST" action="/createAsset" id="create-stock-asset-form">
<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>
<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">
<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-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>
<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">
<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"
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>
<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">
<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-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>
<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">
<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"
>
<div class="mt-6 flex flex-row justify-between">
<button
type="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
</button>
<button
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
</button>
+3 -3
View File
@@ -5,10 +5,10 @@
class="relative flex flex-col bg-white text-gray-700 shadow-md rounded-xl w-full h-full overflow-hidden min-h-[175px]">
<!-- Header Section -->
<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 %>
</p>
<h4 class="planGoal text-xl font-semibold whitespace-nowrap">
<h4 class="planGoal text-2xl font-bold tracking-tight whitespace-nowrap">
<%= thing.retirementAssets %>
</h4>
</div>
@@ -17,7 +17,7 @@
<div class="mt-2">
<div class="p-4 mt-auto border-gray-900">
<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:
</p>
<p class="text-sm font-semibold justify-between text-green-500">
+2 -2
View File
@@ -87,10 +87,10 @@
<li>
<div class="text-center">
<button class="flex flex-row text-white hover:bg-blue-700 focus:ring-4 font-medium rounded-full text-sm focus:outline-none focus:ring-blue-800" type="button" id="drawer-toggle" data-drawer-target="drawer-navigation" data-drawer-placement="right" aria-controls="drawer-navigation">
<svg class="w-[30px] h-[30px] text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
<svg class="w-[30px] h-[30px] text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="white" viewBox="0 0 24 24">
<path fill-rule="evenodd" d="M12 20a7.966 7.966 0 0 1-5.002-1.756l.002.001v-.683c0-1.794 1.492-3.25 3.333-3.25h3.334c1.84 0 3.333 1.456 3.333 3.25v.683A7.966 7.966 0 0 1 12 20ZM2 12C2 6.477 6.477 2 12 2s10 4.477 10 10c0 5.5-4.44 9.963-9.932 10h-.138C6.438 21.962 2 17.5 2 12Zm10-5c-1.84 0-3.333 1.455-3.333 3.25S10.159 13.5 12 13.5c1.84 0 3.333-1.455 3.333-3.25S13.841 7 12 7Z" clip-rule="evenodd"/>
</svg>
<svg class="w-[30px] h-[30px] text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
<svg class="w-[30px] h-[30px] text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="white" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="M5 7h14M5 12h14M5 17h14"/>
</svg>
+3 -3
View File
@@ -34,7 +34,7 @@
</div>
<div>
<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>
@@ -48,7 +48,7 @@
<p class="mt-1 text-md text-white"><%= plan.retirementAge %></p>
</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>
</div>
<div>
@@ -88,7 +88,7 @@
</div>
</div>
<div class="flex items-center justify-center space-x-2">
<button type="button" class="py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 cursor-pointer" onclick="editPlan('<%= plan._id %>')">Edit Plan</button>
<button type="button" class="py-2 px-4 border border-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>
</div>
+16 -94
View File
@@ -21,41 +21,42 @@
<!-- New Plan Modal - Styled like createAsset.ejs -->
<dialog id="newPlanDialog" class="w-lg mx-auto bg-white p-8 mt-10 rounded-lg shadow-md">
<!-- Header: Title and Cancel button -->
<div class="flex flex-row justify-between items-center mb-4">
<div class="flex flex-row justify-between items-center">
<h3 class="text-lg font-bold text-gray-900">Create New Retirement Plan</h3>
<form method="dialog" id="cancelNewPlanDialogForm">
<button type="submit" class="py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 cursor-pointer">Cancel</button>
<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>
</div>
<!-- Form Content -->
<div>
<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>
<label for="name" class="block text-sm font-medium text-gray-700 text-left">Plan Name</label>
<input type="text" name="name" id="planName" placeholder="e.g., My Awesome Plan" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" required>
<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-blue-500 focus:border-blue-500 sm:text-sm" required>
</div>
<div>
<label for="retirementAge" class="block text-sm font-medium text-gray-700 text-left">Desired Retirement Age</label>
<input type="number" name="retirementAge" id="retirementAge" placeholder="e.g., 65" min="18" max="120" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" required>
<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-blue-500 focus:border-blue-500 sm:text-sm" required>
</div>
<div>
<label for="retirementExpenses" class="block text-sm font-medium text-gray-700 text-left">Estimated Monthly Retirement Expenses</label>
<input type="number" name="retirementExpenses" id="retirementExpenses" min="0" placeholder="e.g., 3000" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" required>
<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 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>
<label for="retirementAssets" class="block text-sm font-medium text-gray-700 text-left">Estimated Retirement Assets</label>
<input type="number" name="retirementAssets" id="retirementAssets" min="0" placeholder="e.g., 500000" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" required>
<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 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>
<label for="retirementLiabilities" class="block text-sm font-medium text-gray-700 text-left">Estimated Retirement Liabilities</label>
<input type="number" name="retirementLiabilities" id="retirementLiabilities" min="0" placeholder="e.g., 50000" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" required>
<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 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 id="newPlanError" class="text-red-500 text-sm mt-2" style="display: none;"></div>
<!-- Form Buttons: Save Plan and Reset -->
<div class="mt-6 flex flex-row justify-between">
<button type="button" id="resetNewPlanFormBtn" class="py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">Reset</button>
<button id="submitNewPlanBtn" type="submit" class="py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">Save Plan</button>
<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-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">Save Plan</button>
</div>
</form>
</div>
@@ -64,84 +65,5 @@
<%- include("./partials/navBar") %>
<%- include("./partials/scriptLoader") %>
<script>
document.addEventListener('DOMContentLoaded', () => {
const openModalBtn = document.getElementById('openNewPlanModalBtn');
const newPlanDialog = document.getElementById('newPlanDialog');
const newPlanForm = document.getElementById('newPlanForm');
const submitNewPlanBtn = document.getElementById('submitNewPlanBtn');
const newPlanError = document.getElementById('newPlanError');
const resetNewPlanFormBtn = document.getElementById('resetNewPlanFormBtn');
openModalBtn.addEventListener('click', () => {
newPlanDialog.showModal();
// Error reset is now handled by 'close' event, but good to clear on open too
newPlanError.style.display = 'none';
newPlanError.textContent = '';
});
// Handles form reset and error clearing when dialog is closed by any means (Esc, Cancel button, backdrop click, successful submit)
newPlanDialog.addEventListener('close', () => {
newPlanForm.reset();
newPlanError.style.display = 'none';
newPlanError.textContent = '';
});
// Close dialog if user clicks on the backdrop
newPlanDialog.addEventListener('click', (event) => {
if (event.target === newPlanDialog) {
newPlanDialog.close(); // This will trigger the 'close' event listener above
}
});
resetNewPlanFormBtn.addEventListener('click', () => {
newPlanForm.reset();
newPlanError.style.display = 'none';
newPlanError.textContent = '';
});
newPlanForm.addEventListener('submit', async (event) => {
event.preventDefault();
submitNewPlanBtn.disabled = true;
submitNewPlanBtn.textContent = 'Saving...';
newPlanError.style.display = 'none';
const formData = new FormData(newPlanForm);
const data = Object.fromEntries(formData.entries());
try {
const response = await fetch('/newPlan', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (response.ok) {
const result = await response.json();
if (result.success) {
newPlanDialog.close(); // This will trigger the 'close' event listener
window.location.reload(); // Reload to see the new plan
} else {
newPlanError.textContent = result.message || 'Failed to create plan. Please try again.';
newPlanError.style.display = 'block';
}
} else {
const errorData = await response.json();
newPlanError.textContent = errorData.message || `Error: ${response.status} - ${response.statusText}`;
newPlanError.style.display = 'block';
}
} catch (error) {
console.error('Error submitting new plan:', error);
newPlanError.textContent = 'An unexpected error occurred. Please try again.';
newPlanError.style.display = 'block';
}
finally {
submitNewPlanBtn.disabled = false;
submitNewPlanBtn.textContent = 'Save Plan';
}
});
});
</script>
<script src="/static/scripts/planModals.js"></script>
<%- include("./partials/footer") %>
+21 -19
View File
@@ -51,24 +51,24 @@
<div class="max-w-md mx-auto bg-white p-8 mb-10 rounded-lg shadow-md">
<div class="flex flex-row justify-between">
<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>
<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>
<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>
<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>
<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>
<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>
<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>
</form>
</div>
@@ -78,7 +78,7 @@
<div class="flex flex-row justify-between">
<h3 class="pt-2 pr-2 pb-2">Personal information</h3>
<% 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>
@@ -91,17 +91,19 @@
id="dob"
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 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 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 d = new Date(parseInt(year), parseInt(month), parseInt(day)); %>
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>
<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 == "secondary" ? 'selected' : '' %> value="secondary">Secondary (High School)</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>
<div class="mt-1 space-x-4">
<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>
</label>
<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>
</label>
<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>
</label>
<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>
</label>
</div>
@@ -132,31 +134,31 @@
<div>
<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>
<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>
<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>
<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>
<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>
</form>
<% } else { %>
<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>
<% } %>
+20 -19
View File
@@ -2,17 +2,18 @@
<%- include("./partials/header") %>
<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">
<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">
<div>
<label for="dob" class="block text-sm font-medium text-gray-700">Date of Birth</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">
<label for="dob" class="block text-sm font-medium text-gray-700">Date of Birth <span class="text-red-500">*</span></label>
<% 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>
<label for="education" class="block text-sm font-medium text-gray-700">Education</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">
<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-blue-500 focus:border-blue-500 sm:text-sm">
<option value="primary">Primary (Elementary)</option>
<option value="secondary">Secondary (High School)</option>
<option value="tertiary">Tertiary (College/University)</option>
@@ -20,49 +21,49 @@
</select>
</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">
<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>
</label>
<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>
</label>
<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>
</label>
<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>
</label>
</div>
</div>
<div>
<label for="income" class="block text-sm font-medium text-gray-700">Annual Gross Income</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">
<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-blue-500 focus:border-blue-500 sm:text-sm">
</div>
<div>
<label for="expenses" class="block text-sm font-medium text-gray-700">Monthly Expenses</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">
<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-blue-500 focus:border-blue-500 sm:text-sm">
</div>
<div>
<label for="assets" class="block text-sm font-medium text-gray-700">Net Worth</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">
<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-blue-500 focus:border-blue-500 sm:text-sm">
</div>
<div>
<label for="liabilities" class="block text-sm font-medium text-gray-700">Liabilities</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">
<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-blue-500 focus:border-blue-500 sm:text-sm">
</div>
<div>
<button type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">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>
</form>
</div>
+1 -1
View File
@@ -26,7 +26,7 @@
<% } %>
<div class="mt-5">
<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>
</div>
</div>
+18 -17
View File
@@ -6,27 +6,28 @@
<form action="/signup" method="POST">
<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>
<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">
<label for="name" class="block text-base mb-2 text-slate-600">Name</label>
<input 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"
placeholder="Enter Name" />
<label for="name" class="block text-base mb-2 text-slate-600">Name <span class="text-red-500">*</span></label>
<input required type="text" id="name" name="name"
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" value="<%= typeof name != 'undefined' ? name : '' %>"/>
</div>
<div class="mt-4">
<label for="email" class="block text-base mb-2 text-slate-600">Email</label>
<input 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"
placeholder="Enter Email" />
<label for="email" class="block text-base mb-2 text-slate-600">Email <span class="text-red-500">*</span></label>
<input required type="email" id="email" name="email"
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" value="<%= typeof email != 'undefined' ? email : '' %>"/>
</div>
<div class="mt-4">
<label for="password" class="block text-base mb-2 text-slate-600">Password</label>
<input 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"
<label for="password" class="block text-base mb-2 text-slate-600">Password <span class="text-red-500">*</span></label>
<input required type="password" id="password" name="password"
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" />
<label for="repassword" class="block text-base mb-2 mt-3 text-slate-600">Re-type Password</label>
<input 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"
<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 required type="password" id="repassword" name="repassword"
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" />
</div>
<% if (errMessage && errMessage.length > 0) { %>
@@ -36,11 +37,11 @@
<% } %>
<div class="mt-6">
<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 class="mt-6 text-center">
<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>
</form>