Merge branch from feat/refactor-code

This commit is contained in:
2023-09-11 20:23:51 +07:00
23 changed files with 268 additions and 378 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
import Controller from './controllers/controller'; import Controller from './controllers/index';
import Service from './services/service'; import Service from './services/index';
import View from './views/view'; import View from './views/index';
import 'regenerator-runtime/runtime'; import 'regenerator-runtime/runtime';
import 'core-js/stable'; import 'core-js/stable';
@@ -1,5 +1,3 @@
export const DATABASE_URL = export const DATABASE_URL =
'https://javascript-training-81f7a-default-rtdb.asia-southeast1.firebasedatabase.app'; 'https://javascript-training-81f7a-default-rtdb.asia-southeast1.firebasedatabase.app';
export const TIME_OUT_SEC = 3; export const TIME_OUT_SEC = 3;
export const REGEX_PASSWORD =
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
@@ -1,36 +0,0 @@
export const MESSAGE = {
PASSWORD_NOT_MATCH: 'Password not match! Please try again!',
PASSWORD_NOT_STRONG:
'Password must at least one uppercase, one lowercase letter, one number and one special character!',
ERROR_MESSAGE_DEFAULT: 'Something went wrong!',
TIME_OUT_ERROR: 'Connection time out! Please try again!',
ERROR_CREDENTIAL: {
title: 'Error Credential',
message: 'Email or password not match! Please try again!',
},
DEFAULT_TITLE_ERROR_POPUP: 'Error',
USER_EXIST_ERROR: 'User is exists! Please try another email!',
WELCOME: {
title: 'Login Successful!',
message: 'Welcome to Money Lover!',
},
};
export const URL = {
LOGIN: 'login',
REGISTER: 'register',
HOME: '',
};
export const BTN_CONTENT = {
GOT_IT: 'Got it!',
OK: 'Ok',
};
export const LOCAL_STORAGE = {
ACCESS_TOKEN: 'accessToken',
IS_FIRST_LOGIN: 'isFirstLogin',
};
export const TYPE_POPUP = { success: 'success', error: 'error' };
export const MARK_ICON = { success: 'check', error: 'error' };
@@ -0,0 +1,17 @@
export const PASSWORD_NOT_MATCH = 'Password not match! Please try again!';
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 TIME_OUT_ERROR = 'Connection time out! Please try again!';
export const ERROR_CREDENTIAL = {
title: 'Error Credential',
message: 'Email or password not match! Please try again!',
};
export const DEFAULT_TITLE_ERROR_TOAST = 'Error';
export const USER_EXIST_ERROR = 'User is exists! Please try another email!';
@@ -0,0 +1,20 @@
export const URL = {
LOGIN: 'login',
REGISTER: 'register',
HOME: '',
};
export const BTN_CONTENT = {
GOT_IT: 'Got it!',
OK: 'Ok',
};
export const LOCAL_STORAGE = {
ACCESS_TOKEN: 'accessToken',
};
export const TYPE_TOAST = { success: 'success', error: 'error' };
export const MARK_ICON = { success: 'check', error: 'error' };
export const REGEX_PASSWORD =
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
@@ -4,13 +4,13 @@ export default class LoginController {
this.loginView = view.loginView; this.loginView = view.loginView;
} }
handlerValidateUser(email, password) { handlerLoginUser(email, password) {
return this.service.userService.validateUser(email, password); return this.service.userService.loginUser(email, password);
} }
init() { init() {
if (this.loginView) { if (this.loginView) {
this.loginView.addHandlerForm(this.handlerValidateUser.bind(this)); this.loginView.addHandlerForm(this.handlerLoginUser.bind(this));
} }
} }
} }
@@ -4,7 +4,7 @@ export default class RegisterController {
this.service = service; this.service = service;
} }
hanlderCheckUserExist(email) { handlerCheckUserExist(email) {
return this.service.userService.checkUserExist(email); return this.service.userService.checkUserExist(email);
} }
@@ -15,7 +15,7 @@ export default class RegisterController {
init() { init() {
if (this.registerView) { if (this.registerView) {
this.registerView.addHandlerForm( this.registerView.addHandlerForm(
this.hanlderCheckUserExist.bind(this), this.handlerCheckUserExist.bind(this),
this.handlerSaveUser.bind(this), this.handlerSaveUser.bind(this),
); );
this.registerView.addHandlerInputFormChange(); this.registerView.addHandlerInputFormChange();
@@ -1,6 +1,7 @@
import { MESSAGE } from '../constants/constant'; import * as MESSAGE from '../constants/message';
import { REGEX_PASSWORD, TIME_OUT_SEC } from '../constants/config'; import { TIME_OUT_SEC } from '../constants/config';
import FirebaseService from '../services/firebaseService'; import FirebaseService from '../services/firebaseService';
import { REGEX_PASSWORD } from '../constants/variable';
/** /**
* Validate password * Validate password
@@ -70,6 +71,7 @@ export const getSubdirectoryURL = () => {
const subDirectory = parts[3]; // Get subdirectory url only const subDirectory = parts[3]; // Get subdirectory url only
// Remove query behind subDirectory
const index = subDirectory.indexOf('?'); const index = subDirectory.indexOf('?');
if (index !== -1) { if (index !== -1) {
return subDirectory.substring(0, index); return subDirectory.substring(0, index);
@@ -1,4 +1,4 @@
import { LOCAL_STORAGE } from '../constants/constant'; import { LOCAL_STORAGE } from '../constants/variable';
import { createToken } from '../helpers/helpers'; import { createToken } from '../helpers/helpers';
import CommonService from './commonService'; import CommonService from './commonService';
import LocalStorageService from './localStorageService'; import LocalStorageService from './localStorageService';
@@ -54,7 +54,7 @@ export default class UserService extends CommonService {
* @param {*} password The password user input * @param {*} password The password user input
* @returns {boolean} Return true if match info on database, otherwise return false * @returns {boolean} Return true if match info on database, otherwise return false
*/ */
async validateUser(email, password) { async loginUser(email, password) {
const user = await this.getUserByEmail(email); const user = await this.getUserByEmail(email);
// Check password // Check password
@@ -1,15 +1,13 @@
import CommonView from './commonView'; import CommonView from './commonView';
import * as CONSTANT from '../constants/constant'; import * as MESSAGE from '../constants/message';
import { validatePassword } from '../helpers/helpers'; import { validatePassword } from '../helpers/helpers';
export default class CommonLoginRegisterView extends CommonView { export default class CommonLoginRegisterView extends CommonView {
constructor() { constructor() {
super(); super();
this.handleEventBtnPopupAndOverlay();
this.parentElement = document.querySelector('.form'); this.parentElement = document.querySelector('.form');
this.messageDefault = CONSTANT.MESSAGE.ERROR_MESSAGE_DEFAULT; this.messageDefault = MESSAGE.ERROR_MESSAGE_DEFAULT;
this.inputPassword = document.querySelector('input[name="password"]'); this.inputPassword = document.querySelector('input[name="password"]');
this.inputPasswordConfirm = document.querySelector( this.inputPasswordConfirm = document.querySelector(
'input[name="password_confirm"]', 'input[name="password_confirm"]',
@@ -26,17 +24,17 @@ export default class CommonLoginRegisterView extends CommonView {
if (validatePassword(account.passwordConfirm)) { if (validatePassword(account.passwordConfirm)) {
return true; return true;
} }
this.showError(CONSTANT.MESSAGE.PASSWORD_NOT_STRONG); this.showError(MESSAGE.PASSWORD_NOT_STRONG);
return false; return false;
} }
this.showError(CONSTANT.MESSAGE.PASSWORD_NOT_MATCH); this.showError(MESSAGE.PASSWORD_NOT_MATCH);
return false; return false;
} }
/** /**
* Show or hide style error input password * Show or hide style error input password
*/ */
toogleErrorStyleInputPass() { toggleErrorStyleInputPass() {
this.inputPassword.classList.toggle('error-input'); this.inputPassword.classList.toggle('error-input');
this.inputPasswordConfirm.classList.toggle('error-input'); this.inputPasswordConfirm.classList.toggle('error-input');
} }
@@ -51,7 +49,7 @@ export default class CommonLoginRegisterView extends CommonView {
// If have error message on page, remove it with style error input password // If have error message on page, remove it with style error input password
if (this.errorMessageEl) { if (this.errorMessageEl) {
this.errorMessageEl.remove(); this.errorMessageEl.remove();
this.toogleErrorStyleInputPass(); this.toggleErrorStyleInputPass();
} }
} }
@@ -70,7 +68,11 @@ export default class CommonLoginRegisterView extends CommonView {
*/ */
showError(message) { showError(message) {
this.renderError(message); this.renderError(message);
this.toogleErrorStyleInputPass(); this.toggleErrorStyleInputPass();
}
toggleDialog() {
this.dialog.classList.toggle('active');
} }
/** /**
@@ -86,16 +88,4 @@ export default class CommonLoginRegisterView extends CommonView {
.querySelector('.form__title') .querySelector('.form__title')
.insertAdjacentHTML('afterend', markup); .insertAdjacentHTML('afterend', markup);
} }
/**
* Add event listener for popup and overlay
*/
handleEventBtnPopupAndOverlay() {
this.popupBtn.addEventListener('click', this.tooglePopupForm.bind(this));
this.overlay.addEventListener('click', this.toogleDialog.bind(this));
}
toogleDialog() {
this.dialog.classList.toggle('active');
}
} }
+70 -57
View File
@@ -1,93 +1,106 @@
import { MARK_ICON, TYPE_POPUP } from '../constants/constant'; import { MARK_ICON, TYPE_TOAST } from '../constants/variable';
export default class CommonView { export default class CommonView {
constructor() { constructor() {
this.overlayMarkup = '<div class="overlay"></div>'; this.initToast();
this.initElementToast();
this.initPopup();
this.initElementPopup();
this.initLoader(); this.initLoader();
this.handleEventToast();
} }
/** /**
* Implement popup in site * Implement toast in site
*/ */
initPopup() { initToast() {
this.rootElement = document.querySelector('body'); this.rootElement = document.querySelector('body');
const markup = ` const markup = `
${this.overlayMarkup} <dialog class="dialog">
<div class="modal-box"> <div class="toast">
<div class="mark"></div> <div class="mark"></div>
<h2 class="modal-box__title"></h2> <h2 class="toast__title"></h2>
<p class="modal-box__message"></p> <p class="toast__message"></p>
<button class="toast__redirect-btn">OK</button>
<button class="modal-box__redirect-btn"></button> </div>
</div> </dialog>
`; `;
this.rootElement.insertAdjacentHTML('afterbegin', markup); this.rootElement.insertAdjacentHTML('afterbegin', markup);
} }
/** /**
* Assgign element in popup to property * Assign element in toast to property
*/ */
initElementPopup() { initElementToast() {
this.modalBox = document.querySelector('.modal-box'); this.toastDialog = document.querySelector('.dialog');
this.popupIcon = document.querySelector('.mark'); this.toast = document.querySelector('.toast');
this.popupBtn = document.querySelector('.modal-box__redirect-btn'); this.toastIcon = document.querySelector('.mark');
this.popupTitle = document.querySelector('.modal-box__title'); this.toastBtn = document.querySelector('.toast__redirect-btn');
this.popupContent = document.querySelector('.modal-box__message'); this.toastTitle = document.querySelector('.toast__title');
this.overlay = document.querySelector('.overlay'); this.toastContent = document.querySelector('.toast__message');
} }
/** /**
* Show or hide loader screen * Show or hide loader screen
*/ */
toogleLoaderSpinner() { toggleLoaderSpinner() {
this.spinner.classList.toggle('hidden'); this.spinner.classList.toggle('hidden');
} }
/** /**
* Show or hide popup * Add toast content
* @param {TYPE_TOAST} typeToast Type of the toast
* @param {string} title Title of toast
* @param {string} content Content of toast
* @param {string} btnContent Content of button
*/ */
tooglePopupForm() { initToastContent(typeToast, title, content, btnContent) {
this.overlay.classList.toggle('active'); // Remove old typeToast class if haved
this.modalBox.classList.toggle('active'); this.toast.classList.forEach((classItem) =>
classItem === TYPE_TOAST.success || classItem === TYPE_TOAST.error
? this.toast.classList.remove(classItem)
: '',
);
// Remove old icon toast if haved
this.toastIcon.classList.forEach((classItem) =>
classItem === MARK_ICON.success || classItem === MARK_ICON.error
? this.toastIcon.classList.remove(classItem)
: '',
);
// Init content toast
this.toast.classList.add(
typeToast === TYPE_TOAST.success ? TYPE_TOAST.success : TYPE_TOAST.error,
);
this.toastIcon.classList.add(
typeToast === TYPE_TOAST.success ? MARK_ICON.success : MARK_ICON.error,
);
this.toastTitle.textContent = title;
this.toastContent.textContent = content;
this.toastBtn.textContent = btnContent;
} }
/** /**
* Add popup content * Add event listener for toast
* @param {TYPE_POPUP} typePopup Type of the popup
* @param {string} title Title of popup
* @param {string} content Content of popup
* @param {string} btnContent Content of button
*/ */
initPopupContent(typePopup, title, content, btnContent) { handleEventToast() {
// Remove old typePopup class if haved this.toastBtn.addEventListener('click', () => {
this.modalBox.classList.forEach((classItem) => this.toastDialog.close();
classItem === TYPE_POPUP.success || classItem === TYPE_POPUP.error });
? this.modalBox.classList.remove(classItem)
: '',
);
// Remove old icon popup if haved // Add event close dialog when click outside
this.popupIcon.classList.forEach((classItem) => this.toastDialog.addEventListener('click', (e) => {
classItem === MARK_ICON.success || classItem === MARK_ICON.error const dialogDimensions = this.toastDialog.getBoundingClientRect();
? this.popupIcon.classList.remove(classItem) if (
: '', e.clientX < dialogDimensions.left ||
); e.clientX > dialogDimensions.right ||
e.clientY < dialogDimensions.top ||
// Init content popup e.clientY > dialogDimensions.bottom
this.modalBox.classList.add( ) {
typePopup === TYPE_POPUP.success ? TYPE_POPUP.success : TYPE_POPUP.error, this.toastDialog.close();
); }
this.popupIcon.classList.add( });
typePopup === TYPE_POPUP.success ? MARK_ICON.success : MARK_ICON.error,
);
this.popupTitle.textContent = title;
this.popupContent.textContent = content;
this.popupBtn.textContent = btnContent;
} }
/** /**
+51 -79
View File
@@ -1,4 +1,5 @@
import { TYPE_POPUP, MESSAGE, BTN_CONTENT } from '../constants/constant'; import { TYPE_TOAST, BTN_CONTENT } from '../constants/variable';
import * as MESSAGE from '../constants/message';
import CommonView from './commonView'; import CommonView from './commonView';
import Wallet from '../models/wallet'; import Wallet from '../models/wallet';
@@ -10,19 +11,15 @@ export default class HomeView extends CommonView {
this.allContent = document.querySelectorAll('.app__content-item'); this.allContent = document.querySelectorAll('.app__content-item');
this.addTransactionBtn = document.getElementById('addTransaction'); this.addTransactionBtn = document.getElementById('addTransaction');
this.addBudgetBtn = document.getElementById('addBudget'); this.addBudgetBtn = document.getElementById('addBudget');
this.overlay = document.querySelector('.overlay');
this.darkOverlay = document.querySelector('.dark-overlay');
this.dialog = document.querySelectorAll('.dialog');
this.saveBtn = document.querySelectorAll('.form__save-btn'); this.saveBtn = document.querySelectorAll('.form__save-btn');
this.dialogs = document.querySelectorAll('.dialog');
this.cancelBtn = document.querySelectorAll('.form__cancel-btn'); this.cancelBtn = document.querySelectorAll('.form__cancel-btn');
this.categoryField = document.getElementById('selectCategory'); this.categoryField = document.getElementById('selectCategory');
this.closeIcon = document.querySelector('.close-icon'); this.closeIcon = document.querySelector('.close-icon');
this.budgetForm = document.getElementById('budgetForm'); this.budgetDialog = document.getElementById('budgetDialog');
this.transactionForm = document.getElementById('transactionForm'); this.transactionDialog = document.getElementById('transactionDialog');
this.categoryForm = document.getElementById('categoryForm'); this.categoryDialog = document.getElementById('categoryDialog');
this.walletForm = document.getElementById('walletForm');
this.walletDialog = document.getElementById('walletDialog'); this.walletDialog = document.getElementById('walletDialog');
} }
@@ -38,11 +35,7 @@ export default class HomeView extends CommonView {
// Check user's wallet if have or not // Check user's wallet if have or not
if (!walletExist) { if (!walletExist) {
// Show add wallet dialog // Show add wallet dialog
this.walletDialog.classList.add('active'); this.walletDialog.showModal();
// If overlay is not exist on page
if (!this.overlay.classList.contains('active')) {
this.toggleActiveOverlay();
}
} else { } else {
this.loadEvent(); this.loadEvent();
} }
@@ -50,7 +43,7 @@ export default class HomeView extends CommonView {
} }
addHandlerSubmitWalletForm(saveWallet) { addHandlerSubmitWalletForm(saveWallet) {
this.walletForm.addEventListener('submit', (e) => { this.walletDialog.addEventListener('submit', (e) => {
e.preventDefault(); e.preventDefault();
this.submitWalletForm(saveWallet); this.submitWalletForm(saveWallet);
@@ -68,16 +61,16 @@ export default class HomeView extends CommonView {
await saveWallet(wallet); await saveWallet(wallet);
this.hideDialog(); this.hideDialog();
this.showSuccessPopup('Add wallet success', 'Click ok to continue!'); this.showSuccessToast('Add wallet success', 'Click ok to continue!');
this.loadEvent(); this.loadEvent();
} catch (error) { } catch (error) {
// Show popup error // Show toast error
this.initErrorPopup(error); this.initErrorToast(error);
} }
} }
addHandlerInputChangeWalletForm() { addHandlerInputChangeWalletForm() {
this.walletForm.addEventListener('input', (e) => { this.walletDialog.addEventListener('input', (e) => {
const bodyDialog = e.target.closest('.dialog__body'); const bodyDialog = e.target.closest('.dialog__body');
this.validateWalletForm(bodyDialog); this.validateWalletForm(bodyDialog);
}); });
@@ -95,28 +88,27 @@ export default class HomeView extends CommonView {
} }
} }
showSuccessPopup(title, message) { showSuccessToast(title, message) {
const typePopup = TYPE_POPUP.success; const typeToast = TYPE_TOAST.success;
const btnContent = BTN_CONTENT.OK; const btnContent = BTN_CONTENT.OK;
this.initPopupContent(typePopup, title, message, btnContent); this.initToastContent(typeToast, title, message, btnContent);
// Show popup this.dialogToast.showModal();
this.tooglePopupForm();
} }
/** /**
* Implement error popup in site * Implement error toast in site
* @param {string} content The content will show in error popup * @param {string} content The content will show in error toast
*/ */
initErrorPopup(error) { initErrorToast(error) {
const title = error.title ? error.title : MESSAGE.DEFAULT_TITLE_ERROR_POPUP; const title = error.title ? error.title : MESSAGE.DEFAULT_TITLE_ERROR_TOAST;
const content = error.message ? error.message : error; const content = error.message ? error.message : error;
this.initPopupContent(TYPE_POPUP.error, title, content, BTN_CONTENT.GOT_IT); this.initToastContent(TYPE_TOAST.error, title, content, BTN_CONTENT.GOT_IT);
// Show popup // Show toast
this.tooglePopupForm(); this.toggleToastForm();
} }
/* ------------------------------- HANDLER EVENT ------------------------------- */ /* ------------------------------- HANDLER EVENT ------------------------------- */
@@ -149,66 +141,38 @@ export default class HomeView extends CommonView {
} }
addCommonEventPage() { addCommonEventPage() {
this.addTransactionBtn.addEventListener('click', () => { // Add event close dialog when click outside
this.transactionForm.classList.add('active'); this.dialogs.forEach((dialog) => {
this.toggleActiveOverlay(); dialog.addEventListener('click', (e) => {
}); const dialogDimensions = dialog.getBoundingClientRect();
if (
this.addBudgetBtn.addEventListener('click', () => { e.clientX < dialogDimensions.left ||
this.budgetForm.classList.add('active'); e.clientX > dialogDimensions.right ||
this.toggleActiveOverlay(); e.clientY < dialogDimensions.top ||
}); e.clientY > dialogDimensions.bottom
) {
this.cancelBtn.forEach((item) => { dialog.close();
item.addEventListener('click', () => { }
this.hideDialog();
}); });
}); });
this.overlay.addEventListener('click', () => { this.addTransactionBtn.addEventListener('click', () => {
this.hideDialog(); this.transactionDialog.showModal();
}); });
this.popupBtn.addEventListener('click', this.tooglePopupForm.bind(this)); this.addBudgetBtn.addEventListener('click', () => {
this.budgetDialog.showModal();
});
} }
addEventSelectCategoryDialog() { addEventSelectCategoryDialog() {
this.categoryField.addEventListener('click', () => { this.categoryField.addEventListener('click', () => {
this.categoryForm.classList.add('active'); this.categoryDialog.showModal();
this.toggleDarkOverlayActive();
}); });
this.closeIcon.addEventListener( this.closeIcon.addEventListener('click', () => {
'click', this.categoryDialog.close();
this.hideSelectCategoryForm.bind(this),
);
this.darkOverlay.addEventListener(
'click',
this.hideSelectCategoryForm.bind(this),
);
}
hideSelectCategoryForm() {
this.categoryForm.classList.remove('active');
this.toggleDarkOverlayActive();
}
toggleDarkOverlayActive() {
this.darkOverlay.classList.toggle('active');
}
hideDialog() {
this.dialog.forEach((item) => {
if (item.classList.contains('active')) {
item.classList.remove('active');
}
}); });
this.toggleActiveOverlay();
}
toggleActiveOverlay() {
this.overlay.classList.toggle('active');
} }
removeActiveTab() { removeActiveTab() {
@@ -216,4 +180,12 @@ export default class HomeView extends CommonView {
tab.classList.remove('active'); tab.classList.remove('active');
}); });
} }
toggleDialog() {
this.dialog.forEach((item) => {
if (item.classList.contains('active')) {
item.classList.remove('active');
}
});
}
} }
@@ -1,7 +1,7 @@
import RegisterView from './registerView'; import RegisterView from './registerView';
import LoginView from './loginView'; import LoginView from './loginView';
import { getSubdirectoryURL } from '../helpers/helpers'; import { getSubdirectoryURL } from '../helpers/helpers';
import { URL } from '../constants/constant'; import { URL } from '../constants/variable';
import HomeView from './homeView'; import HomeView from './homeView';
export default class View { export default class View {
+18 -16
View File
@@ -1,12 +1,13 @@
import CommonLoginRegisterView from './commonLoginRegisterView'; import CommonLoginRegisterView from './commonLoginRegisterView';
import { MESSAGE, TYPE_POPUP, BTN_CONTENT } from '../constants/constant'; import { TYPE_TOAST, BTN_CONTENT } from '../constants/variable';
import * as MESSAGE from '../constants/message';
export default class LoginView extends CommonLoginRegisterView { export default class LoginView extends CommonLoginRegisterView {
constructor() { constructor() {
super(); super();
this.parentElement = document.querySelector('.form'); this.parentElement = document.querySelector('.form');
this.dialog = document.querySelector('.modal-box'); this.dialog = document.querySelector('.toast');
} }
/** /**
@@ -24,17 +25,17 @@ export default class LoginView extends CommonLoginRegisterView {
} }
/** /**
* Implement error popup in site * Implement error toast in site
* @param {string} content The content will show in error popup * @param {string} content The content will show in error toast
*/ */
initErrorPopup(error) { initErrorToast(error) {
const title = error.title ? error.title : MESSAGE.DEFAULT_TITLE_ERROR_POPUP; const title = error.title ? error.title : MESSAGE.DEFAULT_TITLE_ERROR_TOAST;
const content = error.message ? error.message : error; const content = error.message ? error.message : error;
this.initPopupContent(TYPE_POPUP.error, title, content, BTN_CONTENT.GOT_IT); this.initToastContent(TYPE_TOAST.error, title, content, BTN_CONTENT.GOT_IT);
// Show popup // Show toast
this.tooglePopupForm(); this.toastDialog.showModal();
} }
/** /**
@@ -51,17 +52,18 @@ export default class LoginView extends CommonLoginRegisterView {
/** /**
* The action when submit form * The action when submit form
* @param {Function} validateUser The function need to be set event * @param {Function} loginUser The function need to be set event
* * @param {event} event The event target
*/ */
async submitForm(validateUser, event) { async submitForm(loginUser, event) {
try { try {
// Load spinner // Load spinner
this.toogleLoaderSpinner(); this.toggleLoaderSpinner();
// Get data from form // Get data from form
const userInput = this.getDataFromForm(event); const userInput = this.getDataFromForm(event);
// Check user exist // Check user exist
const results = await validateUser(userInput.email, userInput.password); const results = await loginUser(userInput.email, userInput.password);
if (results) { if (results) {
window.location.replace('/'); window.location.replace('/');
@@ -70,10 +72,10 @@ export default class LoginView extends CommonLoginRegisterView {
} }
throw MESSAGE.ERROR_CREDENTIAL; throw MESSAGE.ERROR_CREDENTIAL;
} catch (error) { } catch (error) {
// Show popup error // Show toast error
this.initErrorPopup(error); this.initErrorToast(error);
} }
// Close spinner // Close spinner
this.toogleLoaderSpinner(); this.toggleLoaderSpinner();
} }
} }
@@ -1,4 +1,5 @@
import { TYPE_POPUP, MESSAGE, BTN_CONTENT } from '../constants/constant'; import { TYPE_TOAST, BTN_CONTENT } from '../constants/variable';
import * as MESSAGE from '../constants/message';
import CommonLoginRegisterView from './commonLoginRegisterView'; import CommonLoginRegisterView from './commonLoginRegisterView';
import User from '../models/user'; import User from '../models/user';
@@ -6,7 +7,7 @@ export default class RegisterView extends CommonLoginRegisterView {
constructor() { constructor() {
super(); super();
this.dialog = document.querySelector('.modal-box'); this.dialog = document.querySelector('.toast');
} }
/** /**
@@ -32,32 +33,32 @@ export default class RegisterView extends CommonLoginRegisterView {
} }
/** /**
* Implement register success popup in site * Implement register success toast in site
*/ */
showRegisterSuccessPopup() { showRegisterSuccessToast() {
const typePopup = TYPE_POPUP.success; const typeToast = TYPE_TOAST.success;
const title = 'Register Commpleted'; const title = 'Register Commpleted';
const content = 'Please login to continue!'; const content = 'Please login to continue!';
const btnContent = 'OK'; const btnContent = 'OK';
this.initPopupContent(typePopup, title, content, btnContent); this.initToastContent(typeToast, title, content, btnContent);
// Show popup // Show toast
this.tooglePopupForm(); this.toastDialog.showModal();
} }
/** /**
* Implement error popup in site * Implement error toast in site
* @param {string} content The content will show in error popup * @param {string} content The content will show in error toast
*/ */
initErrorPopup(error) { initErrorToast(error) {
const title = error.title ? error.title : MESSAGE.DEFAULT_TITLE_ERROR_POPUP; const title = error.title ? error.title : MESSAGE.DEFAULT_TITLE_ERROR_TOAST;
const content = error.message ? error.message : error; const content = error.message ? error.message : error;
this.initPopupContent(TYPE_POPUP.error, title, content, BTN_CONTENT.OK); this.initToastContent(TYPE_TOAST.error, title, content, BTN_CONTENT.OK);
// Show popup // Show toast
this.tooglePopupForm(); this.toastDialog.showModal();
} }
/** /**
@@ -75,7 +76,7 @@ export default class RegisterView extends CommonLoginRegisterView {
async submitForm(checkExistUser, saveUser) { async submitForm(checkExistUser, saveUser) {
try { try {
// Load spinner // Load spinner
this.toogleLoaderSpinner(); this.toggleLoaderSpinner();
// Get data from form // Get data from form
const user = this.getDataFromForm(); const user = this.getDataFromForm();
@@ -88,16 +89,16 @@ export default class RegisterView extends CommonLoginRegisterView {
throw Error(MESSAGE.USER_EXIST_ERROR); throw Error(MESSAGE.USER_EXIST_ERROR);
} else { } else {
await saveUser(user); await saveUser(user);
// Show popup success // Show toast success
this.showRegisterSuccessPopup(); this.showRegisterSuccessToast();
} }
} }
} catch (error) { } catch (error) {
// Show popup error // Show toast error
this.initErrorPopup(error); this.initErrorToast(error);
} }
// Close spinner // Close spinner
this.toogleLoaderSpinner(); this.toggleLoaderSpinner();
} }
} }
+23 -103
View File
@@ -8,16 +8,16 @@
<title>Money Lover Web</title> <title>Money Lover Web</title>
</head> </head>
<body class="home-page"> <body class="home-page">
<div class="dark-overlay"></div> <dialog class="dialog">
<!-- <div class="modal-box success active"> <div class="toast success">
<div class="mark check"></div> <div class="mark check"></div>
<h2 class="modal-box__title">Login Success!</h2> <h2 class="toast__title">Login Success!</h2>
<p class="modal-box__message">Welcome to Money Lover!</p> <p class="toast__message">Welcome to Money Lover!</p>
<button class="toast__redirect-btn">OK</button>
<button class="modal-box__redirect-btn">OK</button> </div>
</div> --> </dialog>
<!-- Add a wallet form --> <!-- Add a wallet form -->
<div class="dialog" id="walletDialog"> <dialog class="dialog" id="walletDialog">
<div class="dialog__add-wallet"> <div class="dialog__add-wallet">
<!-- Dialog header --> <!-- Dialog header -->
<div class="dialog__header"> <div class="dialog__header">
@@ -62,10 +62,10 @@
</form> </form>
</div> </div>
</div> </div>
</div> </dialog>
<!-- End form --> <!-- End form -->
<!-- Add budget form --> <!-- Add budget form -->
<div class="dialog" id="budgetForm"> <dialog class="dialog" id="budgetDialog">
<div class="dialog__add-budget"> <div class="dialog__add-budget">
<!-- Header --> <!-- Header -->
<div class="dialog__header"> <div class="dialog__header">
@@ -102,16 +102,18 @@
/> />
</div> </div>
<div class="form__action"> <div class="form__action">
<button type="button" class="form__cancel-btn">CANCEL</button> <button formmethod="dialog" class="form__cancel-btn">
CANCEL
</button>
<button type="submit" class="form__save-btn">SAVE</button> <button type="submit" class="form__save-btn">SAVE</button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </dialog>
<!-- End form --> <!-- End form -->
<!-- Add, edit, delete transaction form --> <!-- Add, edit, delete transaction form -->
<div class="dialog" id="transactionForm"> <dialog class="dialog" id="transactionDialog">
<div class="dialog__add-transaction"> <div class="dialog__add-transaction">
<!-- Header--> <!-- Header-->
<div class="dialog__header"> <div class="dialog__header">
@@ -160,20 +162,22 @@
/> />
</div> </div>
<div class="form__action"> <div class="form__action">
<button type="button" class="form__cancel-btn">CANCEL</button> <button formmethod="dialog" class="form__cancel-btn">
CANCEL
</button>
<button type="submit" class="form__save-btn">SAVE</button> <button type="submit" class="form__save-btn">SAVE</button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </dialog>
<!-- End form --> <!-- End form -->
<!-- Select category form --> <!-- Select category form -->
<div class="dialog" id="categoryForm"> <dialog class="dialog" id="categoryDialog">
<div class="dialog__select-category"> <div class="dialog__select-category">
<div class="dialog__header"> <div class="dialog__header">
<div class="dialog__title-container"> <div class="dialog__title-container">
<div class="close-icon"></div> <div formmethod="dialog" class="close-icon"></div>
<div class="dialog__title">Select category</div> <div class="dialog__title">Select category</div>
</div> </div>
<form class="form"> <form class="form">
@@ -268,7 +272,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </dialog>
<!-- End form --> <!-- End form -->
<!-- Start header --> <!-- Start header -->
<header class="header"> <header class="header">
@@ -410,89 +414,5 @@
</div> </div>
<!-- All scripts below is use for test purpose! --> <!-- All scripts below is use for test purpose! -->
<script type="module" src="../js/main.js"></script> <script type="module" src="../js/main.js"></script>
<!-- <script>
// Handle event when click on tabs
const tabs = document.querySelectorAll('.app__tab-item');
const all_content = document.querySelectorAll('.app__content-item');
tabs.forEach((tab, index) => {
tab.addEventListener('click', (e) => {
tabs.forEach((tab) => {
tab.classList.remove('active');
});
tab.classList.add('active');
const line = document.querySelector('.app__line');
line.style.width = e.target.offsetWidth + 'px';
line.style.left = e.target.offsetLeft + 'px';
all_content.forEach((content) => {
content.classList.remove('active');
});
all_content[index].classList.add('active');
});
});
// Add event
const addTransactionBtn = document.getElementById('addTransaction');
const addBudgetBtn = document.getElementById('addBudget');
const overlay = document.querySelector('.overlay');
const darkOverlay = document.querySelector('.dark-overlay');
const dialog = document.querySelectorAll('.dialog');
const cancelBtn = document.querySelectorAll('.form__cancel-btn');
const categoryField = document.getElementById('selectCategory');
const closeIcon = document.querySelector('.close-icon');
const budgetForm = document.getElementById('budgetForm');
const transactionForm = document.getElementById('transactionForm');
const categoryForm = document.getElementById('categoryForm');
overlay.addEventListener('click', () => {
overlay.classList.toggle('active');
dialog.forEach((item) => {
if (item.classList.contains('active')) {
item.classList.remove('active');
}
});
});
addTransactionBtn.addEventListener('click', () => {
transactionForm.classList.toggle('active');
overlay.classList.toggle('active');
});
addBudgetBtn.addEventListener('click', () => {
budgetForm.classList.toggle('active');
overlay.classList.toggle('active');
});
cancelBtn.forEach((item) => {
item.addEventListener('click', () => {
overlay.classList.toggle('active');
dialog.forEach((item) => {
if (item.classList.contains('active')) {
item.classList.remove('active');
}
});
});
});
categoryField.addEventListener('click', () => {
categoryForm.classList.add('active');
darkOverlay.classList.add('active');
});
closeIcon.addEventListener('click', () => {
categoryForm.classList.remove('active');
darkOverlay.classList.remove('active');
});
darkOverlay.addEventListener('click', () => {
darkOverlay.classList.remove('active');
categoryForm.classList.remove('active');
});
</script> -->
</body> </body>
</html> </html>
+1
View File
@@ -3,6 +3,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="../assets/images/favicon.ico" />
<link rel="stylesheet" href="../styles/_main.scss" /> <link rel="stylesheet" href="../styles/_main.scss" />
<title>Login - Money Lover</title> <title>Login - Money Lover</title>
</head> </head>
@@ -3,6 +3,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="shortcut icon" href="../assets/images/favicon.ico" />
<link rel="stylesheet" href="../styles/_main.scss" /> <link rel="stylesheet" href="../styles/_main.scss" />
<title>Register - Money Lover</title> <title>Register - Money Lover</title>
</head> </head>
+1 -1
View File
@@ -12,7 +12,7 @@
@import './components/dialog'; @import './components/dialog';
@import './components/icons'; @import './components/icons';
@import './components/loader'; @import './components/loader';
@import './components/popup-form'; @import './components/toast-form';
// Layouts // Layouts
@import './layouts/header'; @import './layouts/header';
@@ -1,14 +1,9 @@
.dialog { .dialog {
@extend %d-absolute, %rounded-sm; @extend %rounded-sm;
left: 50%; border: none;
top: 32%;
transform: translate(-50%, -50%) scale(1);
background-color: $white; background-color: $white;
z-index: $zindex-lv-2; padding: none;
opacity: 0;
pointer-events: none;
transition: all 0.3s ease; transition: all 0.3s ease;
&__title { &__title {
@@ -17,7 +12,17 @@
line-height: 20px; line-height: 20px;
} }
&.active { &::backdrop {
@extend %active; background-color: rgba($color: #000000, $alpha: 0.2);
}
}
// Login page
.login-page,
.register-page {
.dialog {
@extend %rounded;
padding: 0px;
} }
} }
@@ -1,19 +1,8 @@
.modal-box { .toast {
@extend %d-flex; @extend %d-flex;
@extend %d-absolute;
@include flex-layout($direction: column, $align: center, $gap: 30px); @include flex-layout($direction: column, $align: center, $gap: 30px);
left: 50%;
top: 50%;
transform: translate(-50%, -50%) scale(1);
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
z-index: $zindex-lv-2;
padding: 40px 50px; padding: 40px 50px;
border-radius: 10px;
background-color: $white;
opacity: 0;
pointer-events: none;
transition: all 0.3s ease;
&__title { &__title {
font-size: $fs-2x-lg; font-size: $fs-2x-lg;
@@ -32,17 +21,12 @@
padding: 15px 30px; padding: 15px 30px;
cursor: pointer; cursor: pointer;
} }
&.active {
opacity: 1;
pointer-events: auto;
}
} }
.success .modal-box__redirect-btn { .success .toast__redirect-btn {
background-color: $light-primary-color; background-color: $light-primary-color;
} }
.error .modal-box__redirect-btn { .error .toast__redirect-btn {
background-color: $dark-error-color; background-color: $dark-error-color;
} }