Change validator style

This commit is contained in:
2023-09-17 19:19:07 +07:00
parent 312a009d00
commit 8b0078d1a1
22 changed files with 746 additions and 428 deletions
@@ -23,14 +23,16 @@ export const DEFAULT_CATEGORY = {
INCOME: 'Income',
};
export const REMOVE_CATEGORY = ['Income'];
export const DAY = [
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday',
];
export const MONTH = [
@@ -54,4 +56,6 @@ export const MARK_ICON = { success: 'check', error: 'error' };
export const REGEX = {
PASSWORD:
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
EMAIL:
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
};
@@ -1,9 +1,9 @@
export const PASSWORD_NOT_MATCH = 'Password not match! Please try again!';
export const PASSWORD_NOT_MATCH = 'Password not match!';
export const PASSWORD_NOT_STRONG =
'Password must at least one uppercase, one lowercase letter, one number and one special character!';
export const ERROR_MESSAGE_DEFAULT = 'Something went wrong!';
export const ERROR_MESSAGE_DEFAULT = ['Something went wrong!'];
export const TIME_OUT_ERROR = 'Connection time out! Please try again!';
@@ -21,3 +21,11 @@ export const ADD_WALLET_SUCCESS = 'Add wallet success!';
export const DEFAULT_MESSAGE = 'Press OK to continue!';
export const ADD_TRANSACTION_SUCCESS = 'Add success!';
export const UPDATE_TRANSACTION_SUCCESS = 'Update success!';
export const REGISTER_SUCCESS = 'Register Success';
export const REQUIRED_MESSAGE = (field) => `The ${field} is required!`;
export const INVALID_EMAIL_FORMAT = `The email is not correct!`;
@@ -10,10 +10,6 @@ export default class HomeController {
return this.service.userService.getInfoUserLogin();
}
handlerCheckWalletValid(idUser) {
return this.service.walletService.isValidWallet(idUser);
}
handlerGetWalletUser(idUser) {
return this.service.walletService.getWalletByIdUser(idUser);
}
@@ -46,7 +42,6 @@ export default class HomeController {
if (this.homeView) {
this.homeView.initFunction(
this.handlerGetInfoUserLogin.bind(this),
this.handlerCheckWalletValid.bind(this),
this.handlerGetWalletUser.bind(this),
this.handlerGetAllCategory.bind(this),
this.handlerGetAllTransactions.bind(this),
+24 -6
View File
@@ -7,10 +7,18 @@ import FirebaseService from '../services/firebaseService';
* @param {string} password Password input
* @returns {boolean} Return true if validate password success, otherwise return false
*/
export const isValidatePassword = (password) => {
export const isValidPassword = (password) => {
return REGEX.PASSWORD.test(password);
};
export const isValidateEmail = (email) => {
return String(email).toLowerCase().match(REGEX.EMAIL);
};
export const compare2Password = (password, passwordConfirm) => {
return password === passwordConfirm;
};
/**
* A waiting function with s second
* @param {number} s The time will be waiting
@@ -85,7 +93,7 @@ export const formatNumber = (number) => {
export const changeDateFormat = (oldFormatDate) => {
const tempDate = new Date(oldFormatDate);
const day = DAY[tempDate.getDay() - 1];
const day = DAY[tempDate.getDay()];
const date = tempDate.getDate();
const month = MONTH[tempDate.getMonth()];
const year = tempDate.getFullYear();
@@ -105,9 +113,7 @@ export const createTransactionDetailObject = (category, transactions) => {
return amount;
};
const listTransaction = () => {
const results = [];
transactions.forEach((transaction) => {
const results = transactions.map((transaction) => {
const dateParts = changeDateFormat(transaction.date).split(','); // ['Monday', '14', 'September', '2023']
const day = dateParts[1];
const fullDateString = `${dateParts[0]}, ${dateParts[2]} ${dateParts[3]}`;
@@ -119,7 +125,7 @@ export const createTransactionDetailObject = (category, transactions) => {
amount: transaction.amount,
};
results.push(tempData);
return tempData;
});
results.sort((a, b) => parseInt(b.id, 10) - parseInt(a.id, 10));
@@ -153,3 +159,15 @@ export const getAllTransactionByCategoryName = (categoryName, transactions) => {
return results;
};
export const renderRequiredText = (field, element) => {
const markup = `
<p class="error-text">${MESSAGE.REQUIRED_MESSAGE(field)}</p>
`;
element.insertAdjacentHTML('afterend', markup);
};
export const redirectToLoginPage = () => {
window.location.replace('/login');
};
@@ -10,14 +10,11 @@ export default class CategoryService extends CommonService {
async getAllCategory() {
const data = await this.getAllDataFromPath(this.defaultPath);
const listCategory = [];
if (data) {
data.forEach((category) => {
listCategory.unshift(new Category(category));
return data.reverse().map((category) => {
return new Category(category);
});
return listCategory;
}
return null;
@@ -69,15 +69,12 @@ export default class CommonService {
const results = await timeOutConnect(
this.firebaseService.getAllDataFromPath(path),
);
const listData = [];
if (results) {
// Convert format object
results.forEach((data) => {
listData.push(convertDataObjectToModel(data));
return results.map((data) => {
return convertDataObjectToModel(data);
});
return listData;
}
return null;
@@ -89,15 +86,12 @@ export default class CommonService {
const results = await timeOutConnect(
this.firebaseService.getListDataFromProp(path, property, value),
);
const listData = [];
if (results) {
// Convert format object
results.forEach((data) => {
listData.push(convertDataObjectToModel(data));
});
return listData;
return results.map((data) => {
return convertDataObjectToModel(data);
});
}
return null;
@@ -22,11 +22,7 @@ export default class TransactionService extends CommonService {
this.defaultPath,
);
if (results) {
return results;
}
return null;
return results || null;
}
async deleteTransaction(idTransaction) {
@@ -26,11 +26,7 @@ export default class UserService extends CommonService {
async isValidUser(email) {
const userExist = await this.getUserByEmail(email);
if (userExist) {
return true;
}
return false;
return !!userExist;
}
/**
@@ -41,11 +37,7 @@ export default class UserService extends CommonService {
async getUserByEmail(email) {
const result = await this.getDataFromProp('email', email);
if (result) {
return result;
}
return null;
return result || null;
}
/**
@@ -117,11 +109,7 @@ export default class UserService extends CommonService {
async getUserByToken(accessToken) {
const result = await this.getDataFromProp('accessToken', accessToken);
if (result) {
return result;
}
return null;
return result || null;
}
static clearAccessToken() {
@@ -23,11 +23,7 @@ export default class WalletService extends CommonService {
async isValidWallet(idUser) {
const wallet = await this.getWalletByIdUser(idUser);
if (wallet) {
return true;
}
return false;
return !!wallet;
}
/**
@@ -38,10 +34,6 @@ export default class WalletService extends CommonService {
async getWalletByIdUser(idUser) {
const result = await this.getDataFromProp('idUser', idUser);
if (result) {
return result;
}
return null;
return result || null;
}
}
@@ -0,0 +1,155 @@
import CommonView from './commonView';
import * as MESSAGE from '../constants/message';
import {
compare2Password,
isValidPassword,
isValidateEmail,
renderRequiredText,
} from '../helpers/helpers';
export default class AuthenticationView extends CommonView {
constructor() {
super();
this.parentElement = document.querySelector('.form');
this.emailEl = document.querySelector("[name='email']");
this.messageDefault = MESSAGE.ERROR_MESSAGE_DEFAULT;
this.inputPasswordEl = document.querySelector('input[name="password"]');
this.inputPasswordConfirmEl = document.querySelector(
'input[name="password_confirm"]',
);
this.listError = [];
}
/**
* Validate user input data
* @param {Object} account The account object with email, password, passwordConfirm field
* @returns {boolean} Return true if validate success and return false if validate not success
*/
// isValidateAccount(account) {
// if (account.password === account.passwordConfirm) {
// if (isValidPassword(account.passwordConfirm)) {
// return true;
// }
// this.showError(MESSAGE.PASSWORD_NOT_STRONG);
// return false;
// }
// this.showError(MESSAGE.PASSWORD_NOT_MATCH);
// return false;
// }
// eslint-disable-next-line class-methods-use-this
validateEmail(email) {
if (email) {
if (!isValidateEmail(email)) {
this.listError.push(MESSAGE.INVALID_EMAIL_FORMAT);
return false;
}
return true;
}
renderRequiredText('email', this.emailEl);
return false;
}
validatePassword(password) {
if (password) {
if (!isValidPassword(password)) {
this.listError.push(MESSAGE.PASSWORD_NOT_STRONG);
return false;
}
return true;
}
renderRequiredText('password', this.inputPasswordEl);
return false;
}
validatePasswordConfirm(password, passwordConfirm) {
if (passwordConfirm) {
if (!compare2Password(password, passwordConfirm)) {
this.listError.push(MESSAGE.PASSWORD_NOT_MATCH);
return false;
}
return true;
}
renderRequiredText('confirm password', this.inputPasswordConfirmEl);
return false;
}
/**
* Clear error message at form
*/
clearErrorMessage() {
// Reassign again to check error message element haved on page or not
this.errorMessageEl = document.querySelector('.form__error-message');
this.errorTextEl = document.querySelectorAll('.error-text');
// If have error message on page, remove it with style error input password
if (this.errorMessageEl || this.errorTextEl.length > 0) {
if (this.errorMessageEl) this.errorMessageEl.remove();
if (this.errorTextEl) {
this.errorTextEl.forEach((item) => {
item.remove();
});
}
this.emailEl.classList.remove('error-input');
this.inputPasswordEl.classList.remove('error-input');
if (this.inputPasswordConfirmEl)
this.inputPasswordConfirmEl.classList.remove('error-input');
}
}
/**
* Add event listener for input field at form
*/
addHandlerInputFormChange() {
this.parentElement.addEventListener('input', () => {
this.clearErrorMessage();
});
}
/**
* Show error message with error style input password.
* @param {string} message The error message you want show in form.
*/
showError(message) {
this.renderError(message);
}
toggleDialog() {
this.dialog.classList.toggle('active');
}
/**
* Show error message in form
* @param {string} message The message will show in form
*/
renderError(messages = this.messageDefault) {
if (messages.length > 0) {
const messageItemMarkup = messages
.map((message) => `<li>${message}</li>`)
.join('\n');
const markup = `
<ul class="form__error-message">
${messageItemMarkup}
</ul>
`;
document
.querySelector('.form__title')
.insertAdjacentHTML('afterend', markup);
}
}
}
@@ -1,91 +0,0 @@
import CommonView from './commonView';
import * as MESSAGE from '../constants/message';
import { isValidatePassword } from '../helpers/helpers';
export default class CommonLoginRegisterView extends CommonView {
constructor() {
super();
this.parentElement = document.querySelector('.form');
this.messageDefault = MESSAGE.ERROR_MESSAGE_DEFAULT;
this.inputPassword = document.querySelector('input[name="password"]');
this.inputPasswordConfirm = document.querySelector(
'input[name="password_confirm"]',
);
}
/**
* Validate user input data
* @param {Object} account The account object with email, password, passwordConfirm field
* @returns {boolean} Return true if validate success and return false if validate not success
*/
isValidateAccount(account) {
if (account.password === account.passwordConfirm) {
if (isValidatePassword(account.passwordConfirm)) {
return true;
}
this.showError(MESSAGE.PASSWORD_NOT_STRONG);
return false;
}
this.showError(MESSAGE.PASSWORD_NOT_MATCH);
return false;
}
/**
* Show or hide style error input password
*/
toggleErrorStyleInputPass() {
this.inputPassword.classList.toggle('error-input');
this.inputPasswordConfirm.classList.toggle('error-input');
}
/**
* Clear error message at form
*/
clearErrorMessage() {
// Reassign again to check error message element haved on page or not
this.errorMessageEl = document.querySelector('.form__error-message');
// If have error message on page, remove it with style error input password
if (this.errorMessageEl) {
this.errorMessageEl.remove();
this.toggleErrorStyleInputPass();
}
}
/**
* Add event listener for input field at form
*/
addHandlerInputFormChange() {
this.parentElement.addEventListener('input', () => {
this.clearErrorMessage();
});
}
/**
* Show error message with error style input password.
* @param {string} message The error message you want show in form.
*/
showError(message) {
this.renderError(message);
this.toggleErrorStyleInputPass();
}
toggleDialog() {
this.dialog.classList.toggle('active');
}
/**
* Show error message in form
* @param {string} message The message will show in form
*/
renderError(message = this.messageDefault) {
const markup = `
<p class="form__error-message">${message}</p>
`;
document
.querySelector('.form__title')
.insertAdjacentHTML('afterend', markup);
}
}
+318 -132
View File
@@ -4,6 +4,7 @@ import {
BTN_CONTENT,
DEFAULT_CATEGORY,
FIRST_ADD_WALLET_NOTE,
REMOVE_CATEGORY,
} from '../constants/config';
import * as MESSAGE from '../constants/message';
import CommonView from './commonView';
@@ -14,6 +15,7 @@ import {
formatNumber,
getAllCategoryNameInTransactions,
getAllTransactionByCategoryName,
renderRequiredText,
} from '../helpers/helpers';
import defaultCategoryIcon from '../../assets/images/question-icon.svg';
@@ -44,7 +46,6 @@ export default class HomeView extends CommonView {
initFunction(
getInfoUserLogin,
isValidWallet,
getWalletByIdUser,
getAllCategory,
getAllTransactions,
@@ -54,7 +55,6 @@ export default class HomeView extends CommonView {
deleteTransaction,
) {
this.getInfoUserLogin = getInfoUserLogin;
this.isValidWallet = isValidWallet;
this.getWalletByIdUser = getWalletByIdUser;
this.getAllCategory = getAllCategory;
this.getAllTransactions = getAllTransactions;
@@ -75,14 +75,15 @@ export default class HomeView extends CommonView {
window.location.replace('/login');
} else {
this.user = user;
const wallet = await this.isValidWallet(user.id);
this.wallet = await this.getWalletByIdUser(user.id);
// Check user's wallet if have or not
if (!wallet) {
if (!this.wallet) {
// Show add wallet dialog
this.walletDialog.showModal();
} else {
// Init data
await this.loadTransactionData();
await this.loadData();
// Load event page
@@ -95,7 +96,6 @@ export default class HomeView extends CommonView {
async loadData() {
await this.loadWalletUser();
await this.loadTransactionData();
this.loadSummaryTab();
await this.loadTransactionTab();
}
@@ -126,17 +126,26 @@ export default class HomeView extends CommonView {
this.saveWallet(this.wallet);
}
async updateAmountWallet(amount) {
if (amount >= 0) {
this.wallet.inflow += amount;
} else {
this.wallet.outflow += amount;
}
async updateAmountWallet() {
let inflow = 0;
let outflow = 0;
// Init data first
this.transactionDetails = this.loadTransactionDetailsData();
this.transactionDetails.forEach((transaction) => {
if (transaction.totalAmount >= 0) {
inflow += transaction.totalAmount;
} else {
outflow -= transaction.totalAmount;
}
});
// Reassign value for wallet user;
this.wallet.inflow = inflow;
this.wallet.outflow = -outflow;
await this.saveWallet(this.wallet);
// Reload data
await this.loadData();
}
/**
@@ -144,10 +153,12 @@ export default class HomeView extends CommonView {
* @param {function} getAllCategory Get all category function
*/
async loadCategory() {
this.listCategory = await this.getAllCategory();
if (!this.listCategory) {
this.listCategory = await this.getAllCategory();
}
if (this.listCategory) {
this.renderCategoryItem();
this.renderCategoryList();
}
}
@@ -176,15 +187,16 @@ export default class HomeView extends CommonView {
await this.loadCategory();
// Init data first
const transactionDetails = this.loadTransactionDetailsData();
this.transactionDetails = this.loadTransactionDetailsData();
const transactionEl = document.querySelector('.transaction');
const listTransactionDetailEl =
transactionEl.querySelector('.transaction__list');
const markup = [];
if (transactionDetails) {
transactionDetails.forEach((transactionDetail) => {
if (this.transactionDetails) {
this.transactionDetails.forEach((transactionDetail) => {
markup.push(this.transactionDetailMarkup(transactionDetail));
});
}
@@ -203,9 +215,7 @@ export default class HomeView extends CommonView {
);
// Create transactions details object
const tempList = [];
listCategoryInTransaction.forEach((categoryName) => {
const tempList = listCategoryInTransaction.map((categoryName) => {
// Get category object from list category has been loaded.
const category = this.listCategory.filter(
(item) => item.name === categoryName,
@@ -216,11 +226,9 @@ export default class HomeView extends CommonView {
this.listTransactions,
);
tempList.push(
createTransactionDetailObject(
Object.assign({}, ...category),
transactions,
),
return createTransactionDetailObject(
Object.assign({}, ...category),
transactions,
);
});
@@ -300,27 +308,35 @@ export default class HomeView extends CommonView {
handlerEventTransactionDialog() {
this.transactionDialog.addEventListener('submit', (e) => {
e.preventDefault();
this.clearErrorTransactionDialog();
this.submitTransactionDialog();
this.transactionDialog.close();
});
this.transactionDialog.addEventListener('input', () => {
const dateInput = this.transactionDialog.querySelector(
"[name='selected_date']",
).value;
const categoryName = this.transactionDialog.querySelector(
"[name='category_name']",
).value;
const amountInput =
this.transactionDialog.querySelector("[name='amount']").value;
const saveBtn = this.transactionDialog.querySelector('.form__save-btn');
saveBtn.classList.toggle(
'active',
dateInput && categoryName && amountInput >= 1,
);
this.changeBtnStyleTransactionDialog();
this.clearErrorTransactionDialog();
});
// Add delete transaction event
this.deleteTransactionEvent();
}
clearErrorTransactionDialog() {
// Clear error style
const inputFieldEls =
this.transactionDialog.querySelectorAll('.form__input-field');
const errorTextEls = this.transactionDialog.querySelectorAll('.error-text');
inputFieldEls.forEach((item) => {
if (item.classList.contains('error-input'))
item.classList.remove('error-input');
});
errorTextEls.forEach((item) => {
if (item) item.remove();
});
}
deleteTransactionEvent() {
const deleteTransactionBtn =
this.transactionDialog.querySelector('.form__delete-btn');
@@ -336,9 +352,11 @@ export default class HomeView extends CommonView {
await this.deleteTransaction(idEl.value);
this.showSuccessToast('Delete success!', MESSAGE.DEFAULT_MESSAGE);
// Reload data
await this.loadTransactionData();
await this.loadData();
this.showSuccessToast('Delete success!', MESSAGE.DEFAULT_MESSAGE);
} catch (error) {
this.showErrorToast(error);
}
@@ -347,6 +365,23 @@ export default class HomeView extends CommonView {
});
}
changeBtnStyleTransactionDialog() {
const dateInput = this.transactionDialog.querySelector(
"[name='selected_date']",
).value;
const categoryName = this.transactionDialog.querySelector(
"[name='category_name']",
).value;
const amountInput =
this.transactionDialog.querySelector("[name='amount']").value;
const saveBtn = this.transactionDialog.querySelector('.form__save-btn');
saveBtn.classList.toggle(
'active',
dateInput && categoryName && amountInput >= 1,
);
}
initDataTransactionDialog(idTransaction) {
const transactionArr = this.listTransactions.filter(
(obj) => obj.id === idTransaction,
@@ -379,8 +414,6 @@ export default class HomeView extends CommonView {
}
async submitTransactionDialog() {
this.toggleLoaderSpinner();
try {
const dateEl = this.transactionForm.querySelector(
"[name='selected_date']",
@@ -393,43 +426,87 @@ export default class HomeView extends CommonView {
const idEl = this.transactionForm.querySelector(
"[name='id_transaction']",
);
const transaction = new Transaction({
id: idEl.value,
categoryName: categoryNameEl.value,
date: dateEl.value,
amount: -+amountEl.value,
note: noteEl.value,
idUser: this.wallet.idUser,
});
const amount =
categoryNameEl.value === 'Income' ? +amountEl.value : -+amountEl.value; // Check if this is a income transaction, amount must plus
await this.saveTransaction(transaction);
// Validate data user input
if (
this.validateTransactionForm(dateEl.value, categoryNameEl.value, amount)
) {
this.toggleLoaderSpinner();
this.transactionDialog.close();
// Reload data
await this.loadData();
this.updateAmountWallet(-+amountEl.value);
const transaction = new Transaction({
id: idEl.value,
categoryName: categoryNameEl.value,
date: dateEl.value,
amount,
note: noteEl.value,
idUser: this.wallet.idUser,
});
this.showSuccessToast(
MESSAGE.ADD_TRANSACTION_SUCCESS,
MESSAGE.DEFAULT_MESSAGE,
);
await this.saveTransaction(transaction);
this.clearInputTransactionForm(this.transactionForm);
// Reload data
await this.loadTransactionData();
this.updateAmountWallet();
await this.loadData();
if (!idEl.value) {
// Add success
this.showSuccessToast(
MESSAGE.ADD_TRANSACTION_SUCCESS,
MESSAGE.DEFAULT_MESSAGE,
);
} else {
// Update success
this.showSuccessToast(
MESSAGE.UPDATE_TRANSACTION_SUCCESS,
MESSAGE.DEFAULT_MESSAGE,
);
}
this.toggleLoaderSpinner();
this.clearInputTransactionForm(this.transactionForm);
}
} catch (error) {
this.showErrorToast(error);
this.toggleLoaderSpinner();
}
}
validateTransactionForm(date, categoryName, amount) {
const inputFieldEls =
this.transactionDialog.querySelectorAll('.form__input-field');
if (!date || !categoryName || !amount) {
if (!date) {
renderRequiredText('date', inputFieldEls[0]);
inputFieldEls[0].classList.add('error-input');
}
if (!categoryName) {
renderRequiredText('category', inputFieldEls[1]);
inputFieldEls[1].classList.add('error-input');
}
if (!amount) {
renderRequiredText('amount', inputFieldEls[2]);
inputFieldEls[2].classList.add('error-input');
}
return false;
}
this.toggleLoaderSpinner();
return true;
}
clearInputTransactionForm() {
const categoryIcon = this.transactionForm.querySelector('.category-icon');
categoryIcon.src = defaultCategoryIcon;
this.keySearchCategory = null; // Delete keyword search
this.renderCategoryItem(this.keySearchCategory);
this.renderCategoryList(this.keySearchCategory);
this.transactionForm.reset();
}
// ---------------------END DIALOG---------------------//
@@ -462,15 +539,20 @@ export default class HomeView extends CommonView {
}
// Render category item
this.renderCategoryItem(this.keySearchCategory, newListCategory);
this.renderCategoryList(this.keySearchCategory, newListCategory);
}
renderCategoryItem(categorySelected, listCategory = this.listCategory) {
renderCategoryList(categorySelected, listCategory = this.listCategory) {
// Remove category name unnecessary
const newListCategory = listCategory.filter(
(item) => !REMOVE_CATEGORY.includes(item.name),
);
const listCategoryEl = document.querySelector('.list-category');
listCategoryEl.innerHTML = ''; // Remove old category item
listCategory.forEach((category) => {
newListCategory.forEach((category) => {
const markup = `
<div class="category-item ${
category.name === categorySelected ? 'selected' : ''
@@ -495,56 +577,101 @@ export default class HomeView extends CommonView {
this.budgetDialog.addEventListener('submit', (e) => {
e.preventDefault();
this.clearErrorStyleBudgetForm();
this.submitBudgetForm();
});
this.budgetDialog.addEventListener('input', (e) => {
const bodyDialog = e.target.closest('.dialog__body');
this.validateBudgetForm(bodyDialog);
this.changeBtnStyle(bodyDialog);
this.clearErrorStyleBudgetForm();
});
}
clearErrorStyleBudgetForm() {
// Clear error style
const inputFieldEls =
this.budgetDialog.querySelectorAll('.form__input-field');
const errorTexts = this.budgetDialog.querySelectorAll('.error-text');
inputFieldEls.forEach((item) => {
if (item.classList.contains('error-input'))
item.classList.remove('error-input');
});
errorTexts.forEach((item) => {
if (item) item.remove();
});
}
async submitBudgetForm() {
try {
this.toggleLoaderSpinner(); // Enable loader spinner
this.budgetDialog.close(); // Close dialog
const { formAddBudget } = document.forms;
const form = new FormData(formAddBudget);
const date = form.get('date');
const date = form.get('selected_date');
const amount = form.get('amount');
const note = form.get('note');
const transaction = new Transaction({
categoryName: DEFAULT_CATEGORY.INCOME,
date,
amount: +amount,
note,
idUser: this.wallet.idUser,
});
// Validate data user input
await this.saveTransaction(transaction);
if (this.validateBudgetForm(date, amount)) {
this.toggleLoaderSpinner(); // Enable loader spinner
this.budgetDialog.close(); // Close dialog
// Reload data
await this.loadData();
await this.updateAmountWallet(+amount); // Update wallet
const transaction = new Transaction({
categoryName: DEFAULT_CATEGORY.INCOME,
date,
amount: +amount,
note,
idUser: this.wallet.idUser,
});
// Hide loader spinner
this.toggleLoaderSpinner();
await this.saveTransaction(transaction);
// Show success message
this.showSuccessToast(
MESSAGE.ADD_TRANSACTION_SUCCESS,
MESSAGE.DEFAULT_MESSAGE,
);
// Reload data
await this.loadTransactionData();
await this.updateAmountWallet();
await this.loadData();
document.getElementById('formAddBudget').reset();
// Hide loader spinner
this.toggleLoaderSpinner();
// Show success message
this.showSuccessToast(
MESSAGE.ADD_TRANSACTION_SUCCESS,
MESSAGE.DEFAULT_MESSAGE,
);
document.getElementById('formAddBudget').reset();
}
} catch (error) {
this.showErrorToast(error);
}
}
validateBudgetForm(bodyDialog) {
validateBudgetForm(date, amount) {
const inputFieldEl =
this.budgetDialog.querySelectorAll('.form__input-field');
if (!date || !amount) {
if (!date) {
renderRequiredText('date', inputFieldEl[0]);
inputFieldEl[0].classList.add('error-input');
}
if (!amount) {
renderRequiredText('amount', inputFieldEl[1]);
inputFieldEl[1].classList.add('error-input');
}
return false;
}
return true;
}
changeBtnStyle(bodyDialog) {
const date = bodyDialog.querySelector('.input-date').value;
const amount = +bodyDialog.querySelector('.form__input-balance').value;
const saveBtn = bodyDialog.querySelector('.form__save-btn');
@@ -562,19 +689,34 @@ export default class HomeView extends CommonView {
this.walletDialog.addEventListener('submit', (e) => {
e.preventDefault();
this.clearErrorStyleWalletDialog();
this.submitWalletForm();
});
this.walletDialog.addEventListener('input', (e) => {
const bodyDialog = e.target.closest('.dialog__body');
this.validateWalletForm(bodyDialog);
this.changeBtnStyleWalletDialog(bodyDialog);
this.clearErrorStyleWalletDialog();
});
}
clearErrorStyleWalletDialog() {
const inputFieldEls =
this.walletDialog.querySelectorAll('.form__input-field');
const errorTextEls = this.walletDialog.querySelectorAll('.error-text');
inputFieldEls.forEach((item) => {
if (item.classList.contains('error-input'))
item.classList.remove('error-input');
});
errorTextEls.forEach((item) => {
if (item) item.remove();
});
}
async submitWalletForm() {
this.walletDialog.close();
this.toggleLoaderSpinner();
try {
// Wallet info
const { walletForm } = document.forms;
@@ -582,45 +724,73 @@ export default class HomeView extends CommonView {
const walletName = form.get('walletName');
const amount = form.get('amount');
const wallet = new Wallet({
walletName,
amount: +amount,
idUser: this.user.id,
inflow: +amount,
});
if (this.validateWalletDialog(walletName, amount)) {
this.walletDialog.close();
this.toggleLoaderSpinner();
await this.saveWallet(wallet);
this.wallet = new Wallet({
walletName,
amount: +amount,
idUser: this.user.id,
inflow: +amount,
});
// Transaction info
const transaction = new Transaction({
categoryName: 'Income',
date: new Date().toLocaleDateString('en-US'),
note: FIRST_ADD_WALLET_NOTE,
amount: +amount,
idUser: this.user.id,
});
await this.saveWallet(this.wallet);
await this.saveTransaction(transaction);
// Transaction info
const transaction = new Transaction({
categoryName: 'Income',
date: new Date().toISOString().slice(0, 10),
note: FIRST_ADD_WALLET_NOTE,
amount: +amount,
idUser: this.user.id,
});
// Load data and event
await this.loadData();
this.loadEvent();
await this.saveTransaction(transaction);
this.showSuccessToast(
MESSAGE.ADD_WALLET_SUCCESS,
MESSAGE.DEFAULT_MESSAGE,
);
// Load data and event
await this.loadTransactionData();
await this.loadData();
this.loadEvent();
await this.loadData(); // Load data from database into page
this.showSuccessToast(
MESSAGE.ADD_WALLET_SUCCESS,
MESSAGE.DEFAULT_MESSAGE,
);
await this.loadData(); // Load data from database into page
this.toggleLoaderSpinner();
}
} catch (error) {
// Show toast error
this.showErrorToast(error);
this.toggleLoaderSpinner();
}
this.toggleLoaderSpinner();
}
validateWalletForm(bodyDialog) {
validateWalletDialog(walletName, amount) {
const inputFieldEls =
this.walletDialog.querySelectorAll('.form__input-field');
if (!walletName || !amount) {
if (!walletName) {
renderRequiredText('wallet name', inputFieldEls[0]);
inputFieldEls[0].classList.add('error-input');
}
if (!amount) {
renderRequiredText('amount', inputFieldEls[2]);
inputFieldEls[2].classList.add('error-input');
}
return false;
}
return true;
}
changeBtnStyleWalletDialog(bodyDialog) {
const walletName = bodyDialog.querySelector('.form__input-text').value;
const amount = bodyDialog.querySelector('.form__input-balance').value;
const saveBtn = bodyDialog.querySelector('.form__save-btn');
@@ -671,14 +841,14 @@ export default class HomeView extends CommonView {
*/
handlerTabsTransfer() {
this.tabs.forEach((tab, index) => {
tab.addEventListener('click', () => {
tab.addEventListener('click', (e) => {
this.removeActiveTab();
tab.classList.add('active');
const line = document.querySelector('.app__line');
line.classList.toggle('right', line.classList.contains('left'));
line.classList.toggle('left', !line.classList.contains('right'));
line.style.width = `${e.target.offsetWidth}px`;
line.style.left = `${e.target.offsetLeft}px`;
this.allContent.forEach((content) => {
content.classList.remove('active');
@@ -709,6 +879,10 @@ export default class HomeView extends CommonView {
});
this.addBudgetBtn.addEventListener('click', () => {
// Set default value for date input
this.budgetDialog.querySelector("[name='selected_date']").valueAsDate =
new Date();
this.budgetDialog.showModal();
});
@@ -743,11 +917,20 @@ export default class HomeView extends CommonView {
showTransactionDialog(idTransaction = null) {
this.clearInputTransactionForm();
// Show delete button only if it is a edit form
const deleteBtn = this.transactionDialog.querySelector('.form__delete-btn');
deleteBtn.classList.toggle('hide', !idTransaction);
if (idTransaction) this.initDataTransactionDialog(idTransaction);
if (idTransaction) {
// Init data transaction to dialog
this.initDataTransactionDialog(idTransaction);
} else
this.transactionDialog.querySelector(
"[name='selected_date']",
).valueAsDate = new Date(); // Set default value for date input
// Change style submit btn
this.changeBtnStyleTransactionDialog();
this.transactionDialog.showModal();
}
@@ -785,6 +968,9 @@ export default class HomeView extends CommonView {
// Close select category dialog
this.categoryDialog.close();
// Clear error style for transaction dialog
this.clearErrorTransactionDialog();
}
});
@@ -794,7 +980,7 @@ export default class HomeView extends CommonView {
// Make the keyword search category name into global
this.keySearchCategory = categoryNameEl.value;
this.renderCategoryItem(this.keySearchCategory);
this.renderCategoryList(this.keySearchCategory);
}
this.categoryDialog.showModal();
});
+29 -12
View File
@@ -1,8 +1,8 @@
import CommonLoginRegisterView from './commonLoginRegisterView';
import AuthenticationView from './authenticationView';
import { TYPE_TOAST, BTN_CONTENT } from '../constants/config';
import * as MESSAGE from '../constants/message';
export default class LoginView extends CommonLoginRegisterView {
export default class LoginView extends AuthenticationView {
constructor() {
super();
@@ -26,14 +26,29 @@ export default class LoginView extends CommonLoginRegisterView {
* Get data from user input
* @returns {Object || null} Return object or null
*/
getDataFromForm(event) {
validateForm(event) {
const formData = new FormData(event.target);
const email = formData.get('email');
const password = formData.get('password');
this.account = { email, password };
// Validate user input
this.listError = []; // Reset list error
const emailValid = this.validateEmail(email);
const passwordValid = this.validatePassword(password);
return this.account;
// Show error style
this.emailEl.classList.toggle('error-input', !emailValid);
this.inputPasswordEl.classList.toggle('error-input', !passwordValid);
if (emailValid && passwordValid) {
this.account = { email, password };
return this.account;
}
this.showError(this.listError);
return null;
}
/**
@@ -73,16 +88,18 @@ export default class LoginView extends CommonLoginRegisterView {
this.toggleLoaderSpinner();
// Get data from form
const userInput = this.getDataFromForm(event);
// Check user exist
const results = await loginUser(userInput.email, userInput.password);
const userInput = this.validateForm(event);
if (userInput) {
// Check user exist
const results = await loginUser(userInput.email, userInput.password);
if (results) {
window.location.replace('/');
if (results) {
window.location.replace('/');
return;
return;
}
throw MESSAGE.ERROR_CREDENTIAL;
}
throw MESSAGE.ERROR_CREDENTIAL;
} catch (error) {
// Show toast error
this.initErrorToast(error);
@@ -1,13 +1,15 @@
import { TYPE_TOAST, BTN_CONTENT } from '../constants/config';
import * as MESSAGE from '../constants/message';
import CommonLoginRegisterView from './commonLoginRegisterView';
import AuthenticationView from './authenticationView';
import User from '../models/user';
import { redirectToLoginPage } from '../helpers/helpers';
export default class RegisterView extends CommonLoginRegisterView {
export default class RegisterView extends AuthenticationView {
constructor() {
super();
this.dialog = document.querySelector('.toast');
this.toastBtn = document.querySelector('.toast__redirect-btn');
}
async loadPage(getInfoUserLogin) {
@@ -26,21 +28,38 @@ export default class RegisterView extends CommonLoginRegisterView {
* Get data from user input
* @returns {Object || null} Return object or null
*/
getDataFromForm() {
validateForm() {
const { registerForm } = document.forms;
const formData = new FormData(registerForm);
const email = formData.get('email');
const password = formData.get('password');
const passwordConfirm = formData.get('password_confirm');
const account = { email, password, passwordConfirm };
// Validate user input
this.listError = []; // Reset list error
const emailValid = this.validateEmail(email);
const passwordValid = this.validatePassword(password);
const passwordConfirmValid = this.validatePasswordConfirm(
password,
passwordConfirm,
);
if (this.isValidateAccount(account)) {
const user = new User(account);
// Show error style
this.emailEl.classList.toggle('error-input', !emailValid);
this.inputPasswordEl.classList.toggle('error-input', !passwordValid);
this.inputPasswordConfirmEl.classList.toggle(
'error-input',
!passwordConfirmValid,
);
if (emailValid && passwordValid && passwordConfirmValid) {
const user = new User({ email, password, passwordConfirm });
return user;
}
this.showError(this.listError);
return null;
}
@@ -49,14 +68,17 @@ export default class RegisterView extends CommonLoginRegisterView {
*/
showRegisterSuccessToast() {
const typeToast = TYPE_TOAST.success;
const title = 'Register Commpleted';
const content = 'Please login to continue!';
const btnContent = 'OK';
const title = MESSAGE.REGISTER_SUCCESS;
const content = MESSAGE.DEFAULT_MESSAGE;
const btnContent = BTN_CONTENT.OK;
this.initToastContent(typeToast, title, content, btnContent);
// Show toast
this.toastDialog.showModal();
// Add event for toast button
this.toastBtn.addEventListener('click', redirectToLoginPage);
}
/**
@@ -71,6 +93,9 @@ export default class RegisterView extends CommonLoginRegisterView {
// Show toast
this.toastDialog.showModal();
// Remove event for toast button
this.toastBtn.removeEventListener('click', redirectToLoginPage);
}
/**
@@ -90,8 +115,8 @@ export default class RegisterView extends CommonLoginRegisterView {
// Load spinner
this.toggleLoaderSpinner();
// Get data from form
const user = this.getDataFromForm();
// Get validate form
const user = this.validateForm();
// Save user
if (user) {
@@ -103,6 +128,9 @@ export default class RegisterView extends CommonLoginRegisterView {
await saveUser(user);
// Show toast success
this.showRegisterSuccessToast();
// Clear form
document.getElementById('registerForm').reset();
}
}
} catch (error) {
+79 -67
View File
@@ -18,16 +18,17 @@
<!-- Dialog body -->
<div class="dialog__body">
<form class="form" id="walletForm">
<div class="form__input-field">
<p class="form__label">Wallet name</p>
<input
type="text"
class="form__input-text"
placeholder="Your wallet name?"
name="walletName"
required
minlength="3"
/>
<div class="form__input-container">
<div class="form__input-field">
<p class="form__label">Wallet name</p>
<input
type="text"
class="form__input-text"
placeholder="Your wallet name?"
name="walletName"
minlength="3"
/>
</div>
</div>
<div class="form__currency-balance">
<div class="form__input-field">
@@ -40,14 +41,15 @@
<p class="form__text-currency">United States Dollar</p>
</div>
</div>
<div class="form__input-field">
<p class="form__label">Initial Balance</p>
<input
type="number"
class="form__input-balance"
name="amount"
required
/>
<div class="form__input-container">
<div class="form__input-field">
<p class="form__label">Initial Balance</p>
<input
type="number"
class="form__input-balance"
name="amount"
/>
</div>
</div>
</div>
<button type="submit" class="form__save-btn">SAVE</button>
@@ -67,22 +69,29 @@
<div class="dialog__body">
<form class="form" id="formAddBudget">
<div class="form__date-amount-input">
<div class="form__input-field">
<p class="form__label">Date</p>
<div class="date-input-container">
<input class="input-date" type="date" name="date" required />
<div class="icon-btn"></div>
<div class="form__input-container">
<div class="form__input-field">
<p class="form__label">Date</p>
<div class="date-input-container">
<input
class="input-date"
type="date"
name="selected_date"
/>
<div class="icon-btn"></div>
</div>
</div>
</div>
<div class="form__input-field">
<p class="form__label">Amount</p>
<input
class="form__input-balance"
type="number"
min="1"
name="amount"
required
/>
<div class="form__input-container">
<div class="form__input-field">
<p class="form__label">Amount</p>
<input
class="form__input-balance"
type="number"
min="1"
name="amount"
/>
</div>
</div>
</div>
<div class="form__note-input">
@@ -116,45 +125,48 @@
<form class="form" id="formAddTransaction">
<input type="text" name="id_transaction" hidden />
<div class="form__date-category-amount-input">
<div class="form__input-field">
<p class="form__label">Date</p>
<div class="date-input-container">
<input
class="input-date"
type="date"
name="selected_date"
required
/>
<span class="icon-btn"></span>
<div class="form__input-container">
<div class="form__input-field">
<p class="form__label">Date</p>
<div class="date-input-container">
<input
class="input-date"
type="date"
name="selected_date"
/>
<span class="icon-btn"></span>
</div>
</div>
</div>
<div class="form__input-field" id="selectCategory">
<p class="form__label">Category</p>
<div class="category-input-container">
<img
class="category-icon"
src="../assets/images/question-icon.svg"
alt="Question icon"
/>
<input
type="text"
name="category_name"
class="category-name"
placeholder="Select Category"
required
/>
<div class="icon-btn"></div>
<div class="form__input-container">
<div class="form__input-field" id="selectCategory">
<p class="form__label">Category</p>
<div class="category-input-container">
<img
class="category-icon"
src="../assets/images/question-icon.svg"
alt="Question icon"
/>
<input
type="text"
name="category_name"
class="category-name"
placeholder="Select Category"
/>
<div class="icon-btn"></div>
</div>
</div>
</div>
<div class="form__input-field">
<p class="form__label">Amount</p>
<input
class="form__input-balance"
type="number"
name="amount"
value="0"
required
/>
<div class="form__input-container">
<div class="form__input-field">
<p class="form__label">Amount</p>
<input
class="form__input-balance"
type="number"
name="amount"
value="0"
/>
</div>
</div>
</div>
<div class="form__note-input">
+1 -3
View File
@@ -22,18 +22,16 @@
<h1 class="form__title">Log In</h1>
<p class="form__description">Using Money Lover account</p>
<input
type="email"
type="text"
class="form__input"
placeholder="Email"
name="email"
required
/>
<input
type="password"
name="password"
class="form__input"
placeholder="Password"
required
/>
<button type="submit" class="form__submit-btn">Login</button>
<p class="form__redirect-signup-text">
+25 -21
View File
@@ -21,27 +21,31 @@
<div class="form__container">
<h1 class="form__title">Register</h1>
<p class="form__description">Using Money Lover account</p>
<input
type="email"
class="form__input"
placeholder="Email"
name="email"
required
/>
<input
type="password"
name="password"
class="form__input"
placeholder="Password"
required
/>
<input
type="password"
name="password_confirm"
class="form__input"
placeholder="Confirm Password"
required
/>
<div class="form__input-container">
<input
type="text"
class="form__input"
placeholder="Email"
name="email"
/>
</div>
<div class="form__input-container">
<input
type="password"
name="password"
class="form__input"
placeholder="Password"
/>
</div>
<div class="form__input-container">
<input
type="password"
name="password_confirm"
class="form__input"
placeholder="Confirm Password"
/>
</div>
<button type="submit" class="form__submit-btn">Register</button>
<p class="form__redirect-signup-text">
Have you an account?
@@ -3,7 +3,6 @@
@import './abstracts/variables';
// Bases
@import './bases/base';
@import './bases/placeholder';
@import './bases/reset';
@import './bases/typography';
@@ -1,14 +0,0 @@
.overlay {
@extend %d-absolute;
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.3);
z-index: $zindex-lv-1;
opacity: 0;
pointer-events: none;
&.active {
@extend %active;
}
}
@@ -51,3 +51,9 @@
padding: 8 0px;
}
%error-text {
align-self: flex-start;
padding-left: 5px;
color: $dark-error-color;
}
@@ -143,16 +143,6 @@
background-color: $primary-color;
border-radius: 10px;
transition: all 0.3s ease-in-out;
&.left {
left: 152px;
width: 116px;
}
&.right {
left: 272px;
width: 200px;
}
}
}
@@ -344,6 +334,10 @@
font-size: $fs-md;
line-height: 24px;
}
&__input-field {
height: fit-content;
}
}
}
@@ -364,11 +358,11 @@
@extend %d-flex;
@include flex-layout($gap: 32px);
& .form__input-field:first-child {
& .form__input-container:first-child .form__input-field {
width: 208px;
}
& .form__input-field:last-child {
& .form__input-container:last-child .form__input-field {
width: 240px;
}
}
@@ -504,7 +498,7 @@
&__input-field {
@extend %rounded;
padding: 6px 16px 13.5px 16px;
padding: 6px 16px 12px;
border: 1px solid $gray;
}
@@ -519,6 +513,7 @@
font-size: $fs-lg;
width: 100%;
border: none;
background-color: transparent;
&:focus {
outline: none;
@@ -530,11 +525,12 @@
}
&__input-balance {
font-size: $fs-md;
font-size: $fs-lg;
line-height: 20px;
margin-top: 6px;
height: 28px;
border: none;
width: 100%;
background-color: transparent;
&:focus {
outline: none;
@@ -599,6 +595,7 @@
width: 100%;
border: none;
font-size: $fs-lg;
background-color: transparent;
&:focus {
outline: none;
@@ -614,7 +611,7 @@
position: absolute;
top: 10px;
right: 5px;
background-color: $white;
background-color: transparent;
border-top: 3px solid $dark-gray;
border-right: 3px solid $dark-gray;
border-bottom: none;
@@ -644,6 +641,7 @@
color: $black;
border: none;
caret-color: transparent;
background-color: transparent;
cursor: pointer;
&:focus {
@@ -689,6 +687,24 @@
color: $primary-color;
}
}
.error-input {
background-color: $light-error-color;
border-color: $dark-error-color;
& .form__input-text::placeholder {
color: $white;
}
}
.error-text {
@extend %error-text;
}
.form__input-container {
@extend %d-flex;
@include flex-layout($direction: column, $gap: 7px);
}
}
.close-icon {
@@ -52,6 +52,7 @@
padding: 15px 15px;
border-radius: 10px;
line-height: 22px;
list-style: inside;
}
&__description {
@@ -69,6 +70,11 @@
border: 2px solid $background-input-color;
background-color: $background-input-color;
padding-inline: 14px;
&-container {
@extend %d-flex;
@include flex-layout($direction: column, $gap: 7px);
}
}
&__submit-btn {
@@ -87,4 +93,8 @@
border: 2px solid $dark-error-color;
background-color: $light-error-color;
}
.error-text {
@extend %error-text;
}
}