Merge pull request #9 from Nez27/feat/impl-register-view-and-user-service

Add register view and user service.
This commit is contained in:
Loi Phan
2023-08-30 15:48:14 +07:00
committed by GitHub
10 changed files with 371 additions and 20 deletions
@@ -3,10 +3,7 @@ export const MESSAGE = {
PASSWORD_NOT_STRONG: PASSWORD_NOT_STRONG:
'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!',
}; };
export const TIME_OUT_ERROR = { export const TYPE_POPUP = { success: 'success', error: 'error' };
status: 408,
message: 'Connection time out! Please try again!',
};
export const TYPE_FORM = { success: 'success', error: 'error' };
export const MARK_ICON = { success: 'check', error: 'error' }; export const MARK_ICON = { success: 'check', error: 'error' };
-1
View File
@@ -1 +0,0 @@
console.log('Hello World!');
+20 -12
View File
@@ -1,29 +1,37 @@
import { import { MESSAGE } from '../constants/constant';
TIME_OUT_SEC, import { REGEX_PASSWORD, TIME_OUT_SEC } from '../constants/config';
TIME_OUT_ERROR,
REGEX_PASSWORD,
} from '../constants/constant';
import FirebaseService from '../services/firebaseService'; import FirebaseService from '../services/firebaseService';
/**
* Validate password
* @param {string} password Password input
* @returns {boolean} Return true if validate password success, otherwise return false
*/
export const validatePassword = (password) => { export const validatePassword = (password) => {
return REGEX_PASSWORD.test(password); return REGEX_PASSWORD.test(password);
}; };
/**
* A waiting function with s second
* @param {number} s The time will be waiting
* @returns {Promise} A Promise will be only reject after s second
*/
export const timeout = (s) => { export const timeout = (s) => {
return new Promise((reject) => { return new Promise((_, reject) => {
setTimeout(() => { setTimeout(() => {
reject(TIME_OUT_ERROR); FirebaseService.disconnect();
reject(MESSAGE.TIME_OUT_ERROR);
}, s * 1000); }, s * 1000);
}); });
}; };
/**
* A function waiting the action need to be perform and throw error after s second.
* @param {Function} action The action need to be perform.
* @returns { Object || Error } Return the any object from Firebase or Error
*/
export const timeOutConnect = async (action) => { export const timeOutConnect = async (action) => {
const result = await Promise.race([action, timeout(TIME_OUT_SEC)]); const result = await Promise.race([action, timeout(TIME_OUT_SEC)]);
if (result && result.status === TIME_OUT_ERROR.status) {
FirebaseService.disconnect();
throw new Error(`${result.message}`);
}
return result; return result;
}; };
@@ -12,24 +12,43 @@ import { DATABASE_URL } from '../constants/config';
class FirebaseService { class FirebaseService {
constructor() { constructor() {
const firebaseConfig = { const firebaseConfig = {
DATABASE_URL, databaseURL: DATABASE_URL,
}; };
this.app = initializeApp(firebaseConfig); this.app = initializeApp(firebaseConfig);
this.db = getDatabase(this.app); this.db = getDatabase(this.app);
} }
/**
* Save data in database
* @param {Object} data The object need to save into database
* @param {string} path The path of database need to be save
* @returns {Promise} Return the relsoves when write to database completed
*/
save(data, path) { save(data, path) {
return set(ref(this.db, path), data); return set(ref(this.db, path), data);
} }
/**
* Disconnect to database
*/
disconnect() { disconnect() {
goOffline(this.db); goOffline(this.db);
} }
/**
* Reconnect to database
*/
reconnect() { reconnect() {
goOnline(this.db); goOnline(this.db);
} }
/**
* Find the key 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) { findKeyByPropery(path, property, value) {
return new Promise((resolve) => { return new Promise((resolve) => {
onValue( onValue(
@@ -0,0 +1,7 @@
import UserService from './userService';
export default class Service {
constructor() {
this.userService = new UserService();
}
}
@@ -0,0 +1,39 @@
import { timeOutConnect } from '../helpers/helpers';
import FirebaseService from './firebaseService';
import User from '../models/userModel';
export default class UserService {
constructor() {
this.path = 'users/';
}
/**
* Save user into database
* @param {Object} user The user object need to be saved into databae
*/
async saveUser(user) {
FirebaseService.reconnect();
const saveUser = FirebaseService.save(
user,
this.path + User.createIdUser(),
);
await timeOutConnect(saveUser);
}
/**
* Check user exist in database by email
* @param {string} email Email need to be check
* @returns {boolean} Return true if email exist and otherwise is false
*/
async checkExistUserByEmail(email) {
FirebaseService.reconnect();
const existUser = FirebaseService.findKeyByPropery(
this.path,
'email',
email,
);
const result = await timeOutConnect(existUser);
return result;
}
}
-1
View File
@@ -1 +0,0 @@
// TODO
@@ -0,0 +1,113 @@
import { MARK_ICON, TYPE_POPUP } from '../constants/constant';
export default class CommonView {
constructor() {
this.overlayMarkup = '<div class="overlay"></div>';
this.initPopup();
this.initElementPopup();
this.initLoader();
this.handleEventBtnPopupAndOverlay();
}
/**
* Implement popup in site
*/
initPopup() {
this.rootElement = document.querySelector('body');
const markup = `
${this.overlayMarkup}
<div class="modal-box">
<div class="mark"></div>
<h2 class="modal-box__title"></h2>
<p class="modal-box__message"></p>
<button class="modal-box__redirect-btn"></button>
</div>
`;
this.rootElement.insertAdjacentHTML('afterbegin', markup);
}
/**
* Assgign element in popup to property
*/
initElementPopup() {
this.modalBox = document.querySelector('.modal-box');
this.popupIcon = document.querySelector('.mark');
this.popupBtn = document.querySelector('.modal-box__redirect-btn');
this.popupTitle = document.querySelector('.modal-box__title');
this.popupContent = document.querySelector('.modal-box__message');
this.overlay = document.querySelector('.overlay');
}
/**
* Show or hide loader screen
*/
toogleLoaderSpinner() {
this.spinner.classList.toggle('hidden');
}
/**
* Show or hide popup
*/
tooglePopupForm() {
this.overlay.classList.toggle('active');
this.modalBox.classList.toggle('active');
}
/**
* Add popup content
* @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) {
// Remove old typePopup class if haved
this.modalBox.classList.forEach((classItem) =>
classItem === TYPE_POPUP.success || classItem === TYPE_POPUP.error
? this.modalBox.classList.remove(classItem)
: '',
);
// Remove old icon popup if haved
this.popupIcon.classList.forEach((classItem) =>
classItem === MARK_ICON.success || classItem === MARK_ICON.error
? this.popupIcon.classList.remove(classItem)
: '',
);
// Init content popup
this.modalBox.classList.add(
typePopup === TYPE_POPUP.success ? TYPE_POPUP.success : TYPE_POPUP.error,
);
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;
}
/**
* Add event listener for popup and overlay
*/
handleEventBtnPopupAndOverlay() {
this.popupBtn.addEventListener('click', this.tooglePopupForm.bind(this));
this.overlay.addEventListener('click', this.tooglePopupForm.bind(this));
}
/**
* Implement loader screen
*/
initLoader() {
const markup = `<div class="loader hidden"></div>`;
this.rootElement.insertAdjacentHTML('afterbegin', markup);
// Init element
this.spinner = document.querySelector('.loader');
}
}
@@ -0,0 +1,163 @@
import { validatePassword } from '../helpers/helpers';
import * as CONSTANT from '../constants/constant';
import CommonView from './commonView';
import User from '../models/userModel';
export default class RegisterView extends CommonView {
constructor() {
super();
this.parentElement = document.querySelector('.form');
this.inputField = document.querySelectorAll('.form__input');
this.messageDefault = CONSTANT.MESSAGE.ERROR_MESSAGE_DEFAULT;
}
/**
* Get data from user input
* @returns {Object || null} Return user object or null
*/
getDataFromForm() {
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 };
if (this.validateForm(account)) {
const user = new User(account);
return user;
}
return null;
}
/**
* 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
*/
validateForm(account) {
if (account.password === account.passwordConfirm) {
if (validatePassword(account.passwordConfirm)) {
return true;
}
this.showError(CONSTANT.MESSAGE.PASSWORD_NOT_STRONG);
return false;
}
this.showError(CONSTANT.MESSAGE.PASSWORD_NOT_MATCH);
return false;
}
/**
* Add event listener for form input
* @param {Function} handler The function need to be set event
*/
addHandlerForm(handler) {
this.parentElement.addEventListener('submit', (e) => {
e.preventDefault();
this.clearErrorMessage();
handler();
});
}
/**
* Show error style input password
*/
changeToStyleErrorInputPassword() {
// Index = 1 because it should skip email input field
for (let index = 1; index < this.inputField.length; index += 1) {
this.inputField[index].style.background = '#ffc3c3';
this.inputField[index].style.borderColor = '#ce1414';
}
}
/**
* Clear error style input password
*/
clearStyleErrorInputPassword() {
for (let index = 1; index < this.inputField.length; index += 1) {
this.inputField[index].style.background = '#f5f5f5';
this.inputField[index].style.borderColor = '#f5f5f5';
}
}
/**
* Reassign again to check error message element haved on page or not
*/
reassignVariableErrorMessage() {
this.errorMessage = document.querySelector('.form__error-message');
}
/**
* Clear error message at form
*/
clearErrorMessage() {
this.reassignVariableErrorMessage();
// If have error message on page, remove it
if (this.errorMessage) {
this.errorMessage.remove();
this.clearStyleErrorInputPassword();
}
}
/**
* 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.changeToStyleErrorInputPassword();
}
/**
* Implement register success popup in site
*/
initRegisterSuccessPopup() {
const typePopup = CONSTANT.TYPE_POPUP.success;
const title = 'Register Commpleted';
const content = 'Please login to continue!';
const btnContent = 'OK';
this.initPopupContent(typePopup, title, content, btnContent);
}
/**
* Implement error popup in site
* @param {string} content The content will show in error popup
*/
initErrorPopup(content) {
const typePopup = CONSTANT.TYPE_POPUP.error;
const title = 'Error';
const btnContent = 'Got it!';
this.initPopupContent(typePopup, title, content, btnContent);
}
/**
* Show error message in form
* @param {*} message The message will show in form
*/
renderError(message) {
const markup = `
<p class="form__error-message">${
!message ? this.messageDefault : message
}</p>
`;
document
.querySelector('.form__title')
.insertAdjacentHTML('afterend', markup);
}
}
+7
View File
@@ -0,0 +1,7 @@
import RegisterView from './registerView';
export default class View {
constructor() {
this.registerView = new RegisterView();
}
}