Merge code from feat/javascript-practice branch

This commit is contained in:
2023-09-06 10:04:55 +07:00
13 changed files with 154 additions and 82 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -4,7 +4,16 @@ export const MESSAGE = {
'Password must at least one uppercase, one lowercase letter, one number and one special character!', 'Password must at least one uppercase, one lowercase letter, one number and one special character!',
ERROR_MESSAGE_DEFAULT: 'Something went wrong!', ERROR_MESSAGE_DEFAULT: 'Something went wrong!',
TIME_OUT_ERROR: 'Connection time out! Please try again!', TIME_OUT_ERROR: 'Connection time out! Please try again!',
ERROR_CREDENTIAL: 'Email or password not match! 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!',
};
export const BTN_CONTENT = {
GOT_IT: 'Got it!',
OK: 'Ok',
}; };
export const TYPE_POPUP = { success: 'success', error: 'error' }; export const TYPE_POPUP = { success: 'success', error: 'error' };
export const MARK_ICON = { success: 'check', error: 'error' }; export const MARK_ICON = { success: 'check', error: 'error' };
@@ -4,20 +4,13 @@ export default class LoginController {
this.loginView = view.loginView; this.loginView = view.loginView;
} }
handlerGetUserByEmail(email) { handlerValidateUser(email, password) {
return this.service.userService.getUserByEmail(email); return this.service.userService.validateUser(email, password);
}
handlerCreateTokenUser(user) {
return this.service.userService.createTokenUser(user);
} }
init() { init() {
if (this.loginView.isLoginPage()) { if (this.loginView.isLoginPage()) {
this.loginView.addHandlerForm( this.loginView.addHandlerForm(this.handlerValidateUser.bind(this));
this.handlerGetUserByEmail.bind(this),
this.handlerCreateTokenUser.bind(this),
);
} }
} }
} }
@@ -50,3 +50,15 @@ export const createToken = () => {
} }
return token; return token;
}; };
export const convertModelToDataObject = (model) => {
const { id, ...data } = model;
return { id, data };
};
export const convertDataObjectToModel = (data) => {
const { id, ...object } = data;
return { id, ...object.data };
};
+1
View File
@@ -3,5 +3,6 @@ import App from './app';
// Sure that scripts called after DOM loaded // Sure that scripts called after DOM loaded
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const myApp = new App(); const myApp = new App();
myApp.start(); myApp.start();
}); });
@@ -1,5 +1,6 @@
export default class User { export default class User {
constructor({ email, password, walletName = '', accessToken = '' }) { constructor({ email, password, walletName = '', accessToken = '' }) {
this.id = User.createIdUser();
this.email = email; this.email = email;
this.password = password; this.password = password;
this.walletName = walletName; this.walletName = walletName;
@@ -1,4 +1,8 @@
import { timeOutConnect } from '../helpers/helpers'; import {
convertDataObjectToModel,
convertModelToDataObject,
timeOutConnect,
} from '../helpers/helpers';
import FirebaseService from './firebaseService'; import FirebaseService from './firebaseService';
export default class CommonService { export default class CommonService {
@@ -7,32 +11,59 @@ export default class CommonService {
this.firebaseService = FirebaseService; this.firebaseService = FirebaseService;
} }
/**
* Connect to Firebase Databse
*/
connectToDb() { connectToDb() {
this.firebaseService.reconnect(); this.firebaseService.reconnect();
} }
async findKeyByProperty(property, value, path = this.defaultPath) { async getDataFromProp(property, value, path = this.defaultPath) {
this.connectToDb(); this.connectToDb();
const existUser = this.firebaseService.findKeyByPropery( const existUser = this.firebaseService.getDataFromProp(
path, path,
property, property,
value, value,
); );
const result = await timeOutConnect(existUser); const result = await timeOutConnect(existUser);
return result;
if (result.id && result.data) {
return convertDataObjectToModel(result);
}
return null;
} }
async save(data, path = this.defaultPath) { /**
* Save data on database
* @param {*} data The data wants to save on database
* @param {string} path The path of database
*/
async save(model) {
this.connectToDb(); this.connectToDb();
const saveUser = this.firebaseService.save(data, path); const results = convertModelToDataObject(model);
await timeOutConnect(saveUser);
const saveData = this.firebaseService.save(
results.data,
this.defaultPath + results.id,
);
await timeOutConnect(saveData);
} }
/**
*
* @param {string} id The string of data object
* @param {string} path The path of database
* @returns {Object || null} Return the object if has, otherwise return null
*/
async getDataFromId(id, path = this.defaultPath) { async getDataFromId(id, path = this.defaultPath) {
this.connectToDb(); this.connectToDb();
const result = await this.firebaseService.getDataFromId(id, path); const result = await this.firebaseService.getDataFromId(id, path);
const data = await timeOutConnect(result); const data = await timeOutConnect(result);
if (data) return data; if (data) return data;
return null; return null;
} }
} }
@@ -43,28 +43,30 @@ class FirebaseService {
} }
/** /**
* Find the key of value by property in database * Find id of value by property in database
* @param {string} path The path of database to be found * @param {string} path The path of database to be found
* @param {string} property The property of the value need to be found * @param {string} property The property of the value need to be found
* @param {value} value The value to compare in database * @param {value} value The value to compare in database
* @returns {Promise} Return the relsoves when find completed * @returns {Promise} Return the relsoves when find completed
*/ */
findKeyByPropery(path, property, value) { getDataFromProp(path, property, value) {
return new Promise((resolve) => { return new Promise((resolve) => {
onValue( onValue(
ref(this.db, path), ref(this.db, path),
(snapshot) => { (snapshot) => {
let result; let id;
let data;
// snapshot is a type of data by Firebase define // snapshot is a type of data by Firebase define
snapshot.forEach((childSnapshot) => { snapshot.forEach((childSnapshot) => {
const data = childSnapshot.val(); const dataTemp = childSnapshot.val();
if (data[property] === value) { if (dataTemp[property] === value) {
result = childSnapshot.key; id = childSnapshot.key;
data = dataTemp;
} }
}); });
resolve(result); resolve({ id, data });
}, },
{ {
onlyOnce: true, onlyOnce: true,
@@ -73,6 +75,12 @@ class FirebaseService {
}); });
} }
/**
* Get data object from Id
* @param {string} id The id of data object
* @param {string} path The path of data save in database
* @returns {Promise} Return new Promise
*/
getDataFromId(id, path) { getDataFromId(id, path) {
return new Promise((resolve) => { return new Promise((resolve) => {
onValue( onValue(
@@ -1,5 +1,4 @@
import { createToken } from '../helpers/helpers'; import { createToken } from '../helpers/helpers';
import User from '../models/user';
import CommonService from './commonService'; import CommonService from './commonService';
import LocalStorageService from './localStorageService'; import LocalStorageService from './localStorageService';
@@ -15,17 +14,7 @@ export default class UserService extends CommonService {
* @param {Object} user The user object need to be saved into databae * @param {Object} user The user object need to be saved into databae
*/ */
saveUser(user) { saveUser(user) {
const pathData = this.defaultPath + User.createIdUser(); this.save(user);
this.save(user, pathData);
}
/**
* Get user id by email
* @param {string} email Email need to be check
* @returns {Promise || number} Return id user when exist, otherwise will undefined
*/
getUserIdByEmail(email) {
return this.findKeyByProperty('email', email);
} }
/** /**
@@ -34,10 +23,12 @@ export default class UserService extends CommonService {
* @returns {boolean} Return true if find, otherwise return false * @returns {boolean} Return true if find, otherwise return false
*/ */
async checkUserExist(email) { async checkUserExist(email) {
const userExist = await this.getUserIdByEmail(email); const userExist = await this.getUserByEmail(email);
if (userExist) { if (userExist) {
return true; return true;
} }
return false; return false;
} }
@@ -47,23 +38,49 @@ export default class UserService extends CommonService {
* @returns {Object || null} Return new User Object if find, otherwise return null. * @returns {Object || null} Return new User Object if find, otherwise return null.
*/ */
async getUserByEmail(email) { async getUserByEmail(email) {
const id = await this.getUserIdByEmail(email); const result = await this.getDataFromProp('email', email);
if (id) {
const user = await this.getDataFromId(id); if (result) {
return new User(user); return result;
} }
return null; return null;
} }
async createTokenUser(user) { /**
const id = await this.getUserIdByEmail(user.email); * Validate user info
* @param {string} email The email user input
* @param {*} password The password user input
* @returns {boolean} Return true if match info on database, otherwise return false
*/
async validateUser(email, password) {
const user = await this.getUserByEmail(email);
// Check password
if (user && user.password === password) {
// Create token for user
await this.createTokenUser(email);
return true;
}
return false;
}
/**
* Create token for user on database
* @param {string} email The email user need to be create token
*/
async createTokenUser(email) {
const user = await this.getUserByEmail(email);
const newUserData = user;
// Add token to user object // Add token to user object
const newUserData = new User(user);
newUserData.accessToken = createToken(); newUserData.accessToken = createToken();
this.save(newUserData, this.defaultPath + id); this.save(newUserData);
// Add access token to local storage
LocalStorageService.add('accessToken', newUserData.accessToken); LocalStorageService.add('accessToken', newUserData.accessToken);
} }
} }
+24 -25
View File
@@ -1,21 +1,20 @@
import CommonLoginRegisterView from './commonLoginRegisterView'; import CommonLoginRegisterView from './commonLoginRegisterView';
import * as CONSTANT from '../constants/constant'; import { MESSAGE, TYPE_POPUP, BTN_CONTENT } from '../constants/constant';
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.loginPage = document.getElementById('loginPage'); this.loginPage = document.URL.includes('/login');
} }
/** /**
* Get data from user input * Get data from user input
* @returns {Object || null} Return object or null * @returns {Object || null} Return object or null
*/ */
getDataFromForm() { getDataFromForm(event) {
const { loginForm } = document.forms; const formData = new FormData(event.target);
const formData = new FormData(loginForm);
const email = formData.get('email'); const email = formData.get('email');
const password = formData.get('password'); const password = formData.get('password');
@@ -28,12 +27,11 @@ export default class LoginView extends CommonLoginRegisterView {
* Implement error popup in site * Implement error popup in site
* @param {string} content The content will show in error popup * @param {string} content The content will show in error popup
*/ */
initErrorPopup(content) { initErrorPopup(error) {
const typePopup = CONSTANT.TYPE_POPUP.error; const title = error.title ? error.title : MESSAGE.DEFAULT_TITLE_ERROR_POPUP;
const title = 'Error Credential!'; const content = error.message ? error.message : error;
const btnContent = 'Got it!';
this.initPopupContent(typePopup, title, content, btnContent); this.initPopupContent(TYPE_POPUP.error, title, content, BTN_CONTENT.GOT_IT);
// Show popup // Show popup
this.tooglePopupForm(); this.tooglePopupForm();
@@ -41,35 +39,36 @@ export default class LoginView extends CommonLoginRegisterView {
/** /**
* Add event listener for form input * Add event listener for form input
* @param {Function} handler The function need to be set event * @param {Function} validateUser The function need to be set event
*/ */
addHandlerForm(getUserByEmail, createTokenUser) { addHandlerForm(validateUser) {
this.parentElement.addEventListener('submit', (e) => { this.parentElement.addEventListener('submit', (e) => {
e.preventDefault(); e.preventDefault();
this.clearErrorMessage(); this.clearErrorMessage();
this.submitForm(getUserByEmail, createTokenUser); this.submitForm(validateUser, e);
}); });
} }
async submitForm(getUserByEmail, createTokenUser) { /**
* The action when submit form
* @param {Function} validateUser The function need to be set event
*/
async submitForm(validateUser, event) {
try { try {
// Load spinner // Load spinner
this.toogleLoaderSpinner(); this.toogleLoaderSpinner();
// Get data from form // Get data from form
const userInput = this.getDataFromForm(); const userInput = this.getDataFromForm(event);
// Check user exist // Check user exist
const user = await getUserByEmail(userInput.email); const results = await validateUser(userInput.email, userInput.password);
if (user) { if (results) {
// If user exist, compare password window.location.replace('/');
if (userInput.password === user.password) {
await createTokenUser(user); return;
window.location.replace('/');
return;
}
} }
throw Error(CONSTANT.MESSAGE.ERROR_CREDENTIAL); throw MESSAGE.ERROR_CREDENTIAL;
} catch (error) { } catch (error) {
// Show popup error // Show popup error
this.initErrorPopup(error); this.initErrorPopup(error);
@@ -79,6 +78,6 @@ export default class LoginView extends CommonLoginRegisterView {
} }
isLoginPage() { isLoginPage() {
return this.loginPage !== null; return this.loginPage;
} }
} }
@@ -1,4 +1,4 @@
import { TYPE_POPUP } from '../constants/constant'; import { TYPE_POPUP, MESSAGE, BTN_CONTENT } from '../constants/constant';
import CommonLoginRegisterView from './commonLoginRegisterView'; import CommonLoginRegisterView from './commonLoginRegisterView';
import User from '../models/user'; import User from '../models/user';
@@ -6,7 +6,7 @@ export default class RegisterView extends CommonLoginRegisterView {
constructor() { constructor() {
super(); super();
this.registerPage = document.getElementById('registerPage'); this.registerPage = document.URL.includes('/register');
} }
/** /**
@@ -24,8 +24,10 @@ export default class RegisterView extends CommonLoginRegisterView {
if (this.validateForm(account)) { if (this.validateForm(account)) {
const user = new User(account); const user = new User(account);
return user; return user;
} }
return null; return null;
} }
@@ -48,12 +50,11 @@ export default class RegisterView extends CommonLoginRegisterView {
* Implement error popup in site * Implement error popup in site
* @param {string} content The content will show in error popup * @param {string} content The content will show in error popup
*/ */
initErrorPopup(content) { initErrorPopup(error) {
const typePopup = TYPE_POPUP.error; const title = error.title ? error.title : MESSAGE.DEFAULT_TITLE_ERROR_POPUP;
const title = 'Error'; const content = error.message ? error.message : error;
const btnContent = 'Got it!';
this.initPopupContent(typePopup, title, content, btnContent); this.initPopupContent(TYPE_POPUP.error, title, content, BTN_CONTENT.OK);
// Show popup // Show popup
this.tooglePopupForm(); this.tooglePopupForm();
@@ -84,7 +85,7 @@ export default class RegisterView extends CommonLoginRegisterView {
// Check user exist // Check user exist
const userExist = await checkExistUser(user.email); const userExist = await checkExistUser(user.email);
if (userExist) { if (userExist) {
throw Error('User is exists! Please try another email!'); throw Error(MESSAGE.USER_EXIST_ERROR);
} else { } else {
await saveUser(user); await saveUser(user);
// Show popup success // Show popup success
@@ -101,6 +102,6 @@ export default class RegisterView extends CommonLoginRegisterView {
} }
isRegisterPage() { isRegisterPage() {
return this.registerPage !== null; return this.registerPage;
} }
} }
+1 -1
View File
@@ -7,7 +7,7 @@
<title>Login - Money Lover</title> <title>Login - Money Lover</title>
</head> </head>
<body> <body>
<main id="loginPage"> <main>
<div class="background"> <div class="background">
<div class="wrapper"> <div class="wrapper">
<img <img
+1 -1
View File
@@ -7,7 +7,7 @@
<title>Register - Money Lover</title> <title>Register - Money Lover</title>
</head> </head>
<body> <body>
<main id="registerPage"> <main>
<div class="background"> <div class="background">
<div class="wrapper"> <div class="wrapper">
<img <img