mirror of
https://github.com/Nezumi-2711/javascript-training.git
synced 2026-09-22 20:01:31 +00:00
Merge pull request #13 from Nez27/feat/login-method
Implement login method
This commit is contained in:
@@ -11,5 +11,6 @@ export default class App {
|
||||
|
||||
start() {
|
||||
this.controller.registerController.init();
|
||||
this.controller.loginController.init();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,16 @@ export const MESSAGE = {
|
||||
'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!',
|
||||
};
|
||||
export const BTN_CONTENT = {
|
||||
GOT_IT: 'Got it!',
|
||||
OK: 'Ok',
|
||||
};
|
||||
export const TYPE_POPUP = { success: 'success', error: 'error' };
|
||||
export const MARK_ICON = { success: 'check', error: 'error' };
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import LoginController from './loginController';
|
||||
import RegisterController from './registerController';
|
||||
|
||||
export default class Controller {
|
||||
constructor(service, view) {
|
||||
this.registerController = new RegisterController(service, view);
|
||||
this.loginController = new LoginController(service, view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
export default class LoginController {
|
||||
constructor(service, view) {
|
||||
this.service = service;
|
||||
this.loginView = view.loginView;
|
||||
}
|
||||
|
||||
handlerValidateUser(email, password) {
|
||||
return this.service.userService.validateUser(email, password);
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.loginView.isLoginPage()) {
|
||||
this.loginView.addHandlerForm(this.handlerValidateUser.bind(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,3 +35,30 @@ export const timeOutConnect = async (action) => {
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* A function create token for user
|
||||
* @returns {string} Return token string
|
||||
*/
|
||||
export const createToken = () => {
|
||||
const lengthToken = 36;
|
||||
const chars =
|
||||
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||
let token = '';
|
||||
for (let i = 0; i < lengthToken; i += 1) {
|
||||
token += chars[Math.floor(Math.random() * chars.length)];
|
||||
}
|
||||
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,5 +1,6 @@
|
||||
export default class User {
|
||||
constructor({ email, password, walletName = '' }) {
|
||||
this.id = User.createIdUser();
|
||||
this.email = email;
|
||||
this.password = password;
|
||||
this.walletName = walletName;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { timeOutConnect } from '../helpers/helpers';
|
||||
import {
|
||||
convertDataObjectToModel,
|
||||
convertModelToDataObject,
|
||||
timeOutConnect,
|
||||
} from '../helpers/helpers';
|
||||
import FirebaseService from './firebaseService';
|
||||
|
||||
export default class CommonService {
|
||||
@@ -7,32 +11,56 @@ export default class CommonService {
|
||||
this.firebaseService = FirebaseService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to Firebase Databse
|
||||
*/
|
||||
connectToDb() {
|
||||
this.firebaseService.reconnect();
|
||||
}
|
||||
|
||||
async findKeyByProperty(property, value, path = this.defaultPath) {
|
||||
async getDataFromProp(property, value, path = this.defaultPath) {
|
||||
this.connectToDb();
|
||||
const existUser = this.firebaseService.findKeyByPropery(
|
||||
const existUser = this.firebaseService.getDataFromProp(
|
||||
path,
|
||||
property,
|
||||
value,
|
||||
);
|
||||
const result = await timeOutConnect(existUser);
|
||||
|
||||
return result;
|
||||
if (result.id && result.data) {
|
||||
return convertDataObjectToModel(result);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async save(data, path = this.path) {
|
||||
/**
|
||||
* 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();
|
||||
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);
|
||||
}
|
||||
|
||||
async getDataFromId(id, path = this.path) {
|
||||
/**
|
||||
*
|
||||
* @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) {
|
||||
this.connectToDb();
|
||||
const data = await this.firebaseService.getDataFromId(id, path);
|
||||
const result = await this.firebaseService.getDataFromId(id, path);
|
||||
const data = await timeOutConnect(result);
|
||||
|
||||
if (data) return data;
|
||||
|
||||
|
||||
@@ -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} property The property of the value need to be found
|
||||
* @param {value} value The value to compare in database
|
||||
* @returns {Promise} Return the relsoves when find completed
|
||||
*/
|
||||
findKeyByPropery(path, property, value) {
|
||||
getDataFromProp(path, property, value) {
|
||||
return new Promise((resolve) => {
|
||||
onValue(
|
||||
ref(this.db, path),
|
||||
(snapshot) => {
|
||||
let result;
|
||||
let id;
|
||||
let data;
|
||||
|
||||
// snapshot is a type of data by Firebase define
|
||||
snapshot.forEach((childSnapshot) => {
|
||||
const data = childSnapshot.val();
|
||||
const dataTemp = childSnapshot.val();
|
||||
|
||||
if (data[property] === value) {
|
||||
result = childSnapshot.key;
|
||||
if (dataTemp[property] === value) {
|
||||
id = childSnapshot.key;
|
||||
data = dataTemp;
|
||||
}
|
||||
});
|
||||
resolve(result);
|
||||
resolve({ id, data });
|
||||
},
|
||||
{
|
||||
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) {
|
||||
return new Promise((resolve) => {
|
||||
onValue(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import User from '../models/user';
|
||||
import { createToken } from '../helpers/helpers';
|
||||
import CommonService from './commonService';
|
||||
|
||||
export default class UserService extends CommonService {
|
||||
@@ -13,18 +13,7 @@ export default class UserService extends CommonService {
|
||||
* @param {Object} user The user object need to be saved into databae
|
||||
*/
|
||||
saveUser(user) {
|
||||
const pathData = this.defaultPath + User.createIdUser();
|
||||
|
||||
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);
|
||||
this.save(user);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,7 +22,7 @@ export default class UserService extends CommonService {
|
||||
* @returns {boolean} Return true if find, otherwise return false
|
||||
*/
|
||||
async checkUserExist(email) {
|
||||
const userExist = await this.getUserIdByEmail(email);
|
||||
const userExist = await this.getUserByEmail(email);
|
||||
|
||||
if (userExist) {
|
||||
return true;
|
||||
@@ -48,14 +37,45 @@ export default class UserService extends CommonService {
|
||||
* @returns {Object || null} Return new User Object if find, otherwise return null.
|
||||
*/
|
||||
async getUserByEmail(email) {
|
||||
const id = await this.getUserIdByEmail(email);
|
||||
const result = await this.getDataFromProp('email', email);
|
||||
|
||||
if (id) {
|
||||
const user = await this.getDataFromId(id);
|
||||
|
||||
return new User(user);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
newUserData.accessToken = createToken();
|
||||
this.save(newUserData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import CommonLoginRegisterView from './commonLoginRegisterView';
|
||||
import { MESSAGE, TYPE_POPUP, BTN_CONTENT } from '../constants/constant';
|
||||
|
||||
export default class LoginView extends CommonLoginRegisterView {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.parentElement = document.querySelector('.form');
|
||||
this.loginPage = document.URL.includes('/login');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data from user input
|
||||
* @returns {Object || null} Return object or null
|
||||
*/
|
||||
getDataFromForm(event) {
|
||||
const formData = new FormData(event.target);
|
||||
const email = formData.get('email');
|
||||
const password = formData.get('password');
|
||||
|
||||
this.account = { email, password };
|
||||
|
||||
return this.account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement error popup in site
|
||||
* @param {string} content The content will show in error popup
|
||||
*/
|
||||
initErrorPopup(error) {
|
||||
const title = error.title ? error.title : MESSAGE.DEFAULT_TITLE_ERROR_POPUP;
|
||||
const content = error.message ? error.message : error;
|
||||
|
||||
this.initPopupContent(TYPE_POPUP.error, title, content, BTN_CONTENT.GOT_IT);
|
||||
|
||||
// Show popup
|
||||
this.tooglePopupForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add event listener for form input
|
||||
* @param {Function} validateUser The function need to be set event
|
||||
*/
|
||||
addHandlerForm(validateUser) {
|
||||
this.parentElement.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
this.clearErrorMessage();
|
||||
this.submitForm(validateUser, e);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The action when submit form
|
||||
* @param {Function} validateUser The function need to be set event
|
||||
*/
|
||||
async submitForm(validateUser, event) {
|
||||
try {
|
||||
// Load spinner
|
||||
this.toogleLoaderSpinner();
|
||||
|
||||
// Get data from form
|
||||
const userInput = this.getDataFromForm(event);
|
||||
// Check user exist
|
||||
const results = await validateUser(userInput.email, userInput.password);
|
||||
|
||||
if (results) {
|
||||
window.location.replace('/');
|
||||
|
||||
return;
|
||||
}
|
||||
throw MESSAGE.ERROR_CREDENTIAL;
|
||||
} catch (error) {
|
||||
// Show popup error
|
||||
this.initErrorPopup(error);
|
||||
}
|
||||
// Close spinner
|
||||
this.toogleLoaderSpinner();
|
||||
}
|
||||
|
||||
isLoginPage() {
|
||||
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 User from '../models/user';
|
||||
|
||||
@@ -6,7 +6,7 @@ export default class RegisterView extends CommonLoginRegisterView {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.registerPage = document.getElementById('registerPage');
|
||||
this.registerPage = document.URL.includes('/register');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,12 +50,11 @@ export default class RegisterView extends CommonLoginRegisterView {
|
||||
* Implement error popup in site
|
||||
* @param {string} content The content will show in error popup
|
||||
*/
|
||||
initErrorPopup(content) {
|
||||
const typePopup = TYPE_POPUP.error;
|
||||
const title = 'Error';
|
||||
const btnContent = 'Got it!';
|
||||
initErrorPopup(error) {
|
||||
const title = error.title ? error.title : MESSAGE.DEFAULT_TITLE_ERROR_POPUP;
|
||||
const content = error.message ? error.message : error;
|
||||
|
||||
this.initPopupContent(typePopup, title, content, btnContent);
|
||||
this.initPopupContent(TYPE_POPUP.error, title, content, BTN_CONTENT.OK);
|
||||
|
||||
// Show popup
|
||||
this.tooglePopupForm();
|
||||
@@ -86,7 +85,7 @@ export default class RegisterView extends CommonLoginRegisterView {
|
||||
// Check user exist
|
||||
const userExist = await checkExistUser(user.email);
|
||||
if (userExist) {
|
||||
throw Error('User is exists! Please try another email!');
|
||||
throw Error(MESSAGE.USER_EXIST_ERROR);
|
||||
} else {
|
||||
await saveUser(user);
|
||||
// Show popup success
|
||||
@@ -103,6 +102,6 @@ export default class RegisterView extends CommonLoginRegisterView {
|
||||
}
|
||||
|
||||
isRegisterPage() {
|
||||
return this.registerPage !== null;
|
||||
return this.registerPage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import RegisterView from './registerView';
|
||||
import LoginView from './loginView';
|
||||
|
||||
export default class View {
|
||||
constructor() {
|
||||
this.registerView = new RegisterView();
|
||||
this.loginView = new LoginView();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<title>Login - Money Lover</title>
|
||||
</head>
|
||||
<body>
|
||||
<main id="loginPage">
|
||||
<main>
|
||||
<div class="background">
|
||||
<div class="wrapper">
|
||||
<img
|
||||
@@ -16,7 +16,7 @@
|
||||
alt="Logo web"
|
||||
/>
|
||||
<!-- Log in form -->
|
||||
<form action="#" class="form" method="post">
|
||||
<form action="#" class="form" method="post" id="loginForm">
|
||||
<div class="form__container">
|
||||
<h1 class="form__title">Log In</h1>
|
||||
<p class="form__description">Using Money Lover account</p>
|
||||
@@ -45,5 +45,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<script type="module" src="../js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<title>Register - Money Lover</title>
|
||||
</head>
|
||||
<body>
|
||||
<main id="registerPage">
|
||||
<main>
|
||||
<div class="background">
|
||||
<div class="wrapper">
|
||||
<img
|
||||
|
||||
Reference in New Issue
Block a user