From 6fa2d772b7f78533f64a538ba4f06e285492e58d Mon Sep 17 00:00:00 2001 From: Loi Phan Date: Sat, 30 Sep 2023 16:18:37 +0700 Subject: [PATCH] Add login method --- typescript-practice/src/pages/login.html | 1 + typescript-practice/src/ts/app.ts | 7 +- .../src/ts/constants/config.ts | 6 + .../src/ts/controllers/index.ts | 4 + .../src/ts/controllers/loginController.ts | 31 +++++ .../src/ts/controllers/registerController.ts | 4 +- typescript-practice/src/ts/global/types.ts | 9 ++ .../src/ts/helpers/redirect.ts | 4 - typescript-practice/src/ts/helpers/url.ts | 16 +++ typescript-practice/src/ts/models/user.ts | 36 ++--- .../src/ts/services/firebaseService.ts | 24 ++-- .../src/ts/services/localStorageService.ts | 12 +- .../src/ts/services/userService.ts | 6 +- typescript-practice/src/ts/views/index.ts | 17 ++- typescript-practice/src/ts/views/loginView.ts | 127 ++++++++++++++++++ .../src/ts/views/registerView.ts | 4 +- 16 files changed, 256 insertions(+), 52 deletions(-) create mode 100644 typescript-practice/src/ts/controllers/loginController.ts delete mode 100644 typescript-practice/src/ts/helpers/redirect.ts create mode 100644 typescript-practice/src/ts/helpers/url.ts create mode 100644 typescript-practice/src/ts/views/loginView.ts diff --git a/typescript-practice/src/pages/login.html b/typescript-practice/src/pages/login.html index 65fe9ac..a914c9d 100644 --- a/typescript-practice/src/pages/login.html +++ b/typescript-practice/src/pages/login.html @@ -44,5 +44,6 @@ + diff --git a/typescript-practice/src/ts/app.ts b/typescript-practice/src/ts/app.ts index 4c65fe6..ab0e82d 100644 --- a/typescript-practice/src/ts/app.ts +++ b/typescript-practice/src/ts/app.ts @@ -3,13 +3,14 @@ import Service from './services/index'; import View from './views/index'; export default class App { - private controller: Controller; + private _controller: Controller; constructor() { - this.controller = new Controller(new Service(), new View()); + this._controller = new Controller(new Service(), new View()); } start() { - this.controller.registerController.init(); + this._controller.registerController.init(); + this._controller.loginController.init(); } } diff --git a/typescript-practice/src/ts/constants/config.ts b/typescript-practice/src/ts/constants/config.ts index e1d583b..863c954 100644 --- a/typescript-practice/src/ts/constants/config.ts +++ b/typescript-practice/src/ts/constants/config.ts @@ -26,3 +26,9 @@ export const REGEX = { 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,}))$/, }; + +export const URL = { + LOGIN: 'login', + REGISTER: 'register', + HOME: '', +}; diff --git a/typescript-practice/src/ts/controllers/index.ts b/typescript-practice/src/ts/controllers/index.ts index 390bfc6..c9000fa 100644 --- a/typescript-practice/src/ts/controllers/index.ts +++ b/typescript-practice/src/ts/controllers/index.ts @@ -1,14 +1,18 @@ import Service from 'services'; import RegisterController from './registerController'; import View from 'views'; +import LoginController from './loginController'; export default class Controller { public registerController: RegisterController; + public loginController: LoginController; + constructor( public service: Service, public view: View, ) { this.registerController = new RegisterController(service, view); + this.loginController = new LoginController(service, view); } } diff --git a/typescript-practice/src/ts/controllers/loginController.ts b/typescript-practice/src/ts/controllers/loginController.ts new file mode 100644 index 0000000..cf1a724 --- /dev/null +++ b/typescript-practice/src/ts/controllers/loginController.ts @@ -0,0 +1,31 @@ +import User from 'models/user'; +import Service from 'services'; +import View from 'views'; +import LoginView from 'views/loginView'; + +export default class LoginController { + public loginView: LoginView | null = null; + + constructor( + public service: Service, + public view: View, + ) { + this.service = service; + this.loginView = view.loginView; + } + + handlerLoginUser(email: string, password: string): Promise { + return this.service.userService.loginUser(email, password); + } + + handlerGetInfoUserLogin(): Promise { + return this.service.userService.getInfoUserLogin(); + } + + init() { + if (this.loginView) { + this.loginView.loadPage(this.handlerGetInfoUserLogin.bind(this)); + this.loginView.addHandlerForm(this.handlerLoginUser.bind(this)); + } + } +} diff --git a/typescript-practice/src/ts/controllers/registerController.ts b/typescript-practice/src/ts/controllers/registerController.ts index a8b9a27..642173b 100644 --- a/typescript-practice/src/ts/controllers/registerController.ts +++ b/typescript-practice/src/ts/controllers/registerController.ts @@ -4,14 +4,14 @@ import View from 'views'; import RegisterView from 'views/registerView'; export default class RegisterController { - public registerView: RegisterView; + public registerView: RegisterView | null = null; constructor( public service: Service, public view: View, ) { - this.registerView = view.registerView; this.service = service; + this.registerView = view.registerView; } handlerCheckUserValid(email: string): Promise { diff --git a/typescript-practice/src/ts/global/types.ts b/typescript-practice/src/ts/global/types.ts index 1094600..776e11d 100644 --- a/typescript-practice/src/ts/global/types.ts +++ b/typescript-practice/src/ts/global/types.ts @@ -18,3 +18,12 @@ export type TError = { title: string; message: string; }; + +export class CustomError extends Error { + constructor( + public title: string, + message?: string, + ) { + super(message); + } +} diff --git a/typescript-practice/src/ts/helpers/redirect.ts b/typescript-practice/src/ts/helpers/redirect.ts deleted file mode 100644 index d6584cc..0000000 --- a/typescript-practice/src/ts/helpers/redirect.ts +++ /dev/null @@ -1,4 +0,0 @@ -// eslint-disable-next-line import/prefer-default-export -export const redirectToLoginPage = (): void => { - window.location.replace('/login'); -}; diff --git a/typescript-practice/src/ts/helpers/url.ts b/typescript-practice/src/ts/helpers/url.ts new file mode 100644 index 0000000..bf01a04 --- /dev/null +++ b/typescript-practice/src/ts/helpers/url.ts @@ -0,0 +1,16 @@ +export const redirectToLoginPage = (): void => { + window.location.replace('/login'); +}; + +export const getSubdirectoryURL = () => { + const url = window.location.href; + const parts = url.split('/'); // Results: ['http:', '', 'example.com', ''] + const subDirectory = parts[3]; // Get subdirectory url only + const index = subDirectory.indexOf('?'); // Remove query behind subDirectory + + if (index !== -1) { + return subDirectory.substring(0, index); + } + + return subDirectory; +}; diff --git a/typescript-practice/src/ts/models/user.ts b/typescript-practice/src/ts/models/user.ts index 808a41a..43f8dbf 100644 --- a/typescript-practice/src/ts/models/user.ts +++ b/typescript-practice/src/ts/models/user.ts @@ -1,38 +1,38 @@ import { createIdUser } from '../helpers/data'; export default class User { - private readonly id: number; + private readonly _id: number; - private email: string; + private _email: string; - private password: string; + private _password: string; - private accessToken: string; + private _accessToken: string; constructor(email: string, password: string, accessToken?: string) { - this.id = createIdUser(); - this.email = email; - this.password = password || ''; - this.accessToken = accessToken || ''; + this._id = createIdUser(); + this._email = email; + this._password = password || ''; + this._accessToken = accessToken || ''; } - get getPassword() { - return this.password; + get password() { + return this._password; } - set setAccessToken(accessToken: string) { - this.accessToken = accessToken; + set accessToken(accessToken: string) { + this._accessToken = accessToken; } - get getAccessToken() { - return this.accessToken; + get accessToken() { + return this._accessToken; } - get getEmail() { - return this.email; + get email() { + return this._email; } - get getId() { - return this.id; + get id() { + return this._id; } } diff --git a/typescript-practice/src/ts/services/firebaseService.ts b/typescript-practice/src/ts/services/firebaseService.ts index 427ee41..17176a8 100644 --- a/typescript-practice/src/ts/services/firebaseService.ts +++ b/typescript-practice/src/ts/services/firebaseService.ts @@ -12,16 +12,16 @@ import { import { DATABASE_URL } from '../constants/config'; class FirebaseService { - private app: FirebaseApp; + private _app: FirebaseApp; - private db: Database; + private _db: Database; constructor() { const firebaseConfig = { databaseURL: DATABASE_URL, }; - this.app = initializeApp(firebaseConfig); - this.db = getDatabase(this.app); + this._app = initializeApp(firebaseConfig); + this._db = getDatabase(this._app); } /** @@ -31,25 +31,25 @@ class FirebaseService { * @returns {Promise} Return the resolves when write to database completed */ save(data: object, path: string): Promise { - return set(ref(this.db, path), data); + return set(ref(this._db, path), data); } delete(id: string, path: string): Promise { - return remove(ref(this.db, path + id)); + return remove(ref(this._db, path + id)); } /** * Disconnect to database */ disconnect(): void { - goOffline(this.db); + goOffline(this._db); } /** * Reconnect to database */ reconnect(): void { - goOnline(this.db); + goOnline(this._db); } /** @@ -66,7 +66,7 @@ class FirebaseService { ): Promise { return new Promise((resolve) => { onValue( - ref(this.db, path), + ref(this._db, path), (snapshot) => { let id: string | null = null; let data: object | null = null; @@ -104,7 +104,7 @@ class FirebaseService { getDataFromId(id: string, path: string): Promise { return new Promise((resolve) => { onValue( - ref(this.db, path + id), + ref(this._db, path + id), (snapshot) => { resolve(snapshot.val()); }, @@ -118,7 +118,7 @@ class FirebaseService { getAllDataFromPath(path: string): Promise { return new Promise((resolve) => { onValue( - ref(this.db, path), + ref(this._db, path), (snapshot) => { const listData: object[] = []; @@ -149,7 +149,7 @@ class FirebaseService { ): Promise { return new Promise((resolve) => { onValue( - ref(this.db, path), + ref(this._db, path), (snapshot) => { let id: string; let data: object; diff --git a/typescript-practice/src/ts/services/localStorageService.ts b/typescript-practice/src/ts/services/localStorageService.ts index 607ea5e..9ef2187 100644 --- a/typescript-practice/src/ts/services/localStorageService.ts +++ b/typescript-practice/src/ts/services/localStorageService.ts @@ -1,24 +1,24 @@ class LocalStorageService { - private localStorage: Storage; + private _localStorage: Storage; constructor() { - this.localStorage = localStorage; + this._localStorage = localStorage; } add(key: string, value: string): void { - this.localStorage.setItem(key, value); + this._localStorage.setItem(key, value); } get(key: string): string | null { - return this.localStorage.getItem(key); + return this._localStorage.getItem(key); } remove(key: string): void { - this.localStorage.removeItem(key); + this._localStorage.removeItem(key); } clear(): void { - this.localStorage.clear(); + this._localStorage.clear(); } } diff --git a/typescript-practice/src/ts/services/userService.ts b/typescript-practice/src/ts/services/userService.ts index 1abf86f..6946368 100644 --- a/typescript-practice/src/ts/services/userService.ts +++ b/typescript-practice/src/ts/services/userService.ts @@ -51,7 +51,7 @@ export default class UserService { const user = await this.getUserByEmail(email); // Check password - if (user && user.getPassword === password) { + if (user && user.password === password) { // Create token for user await this.createTokenUser(email); @@ -71,14 +71,14 @@ export default class UserService { const newUserData = user; // Add token to user object - newUserData.setAccessToken = createToken(); + newUserData.accessToken = createToken(); this._commonService.save(newUserData); // Add access token to local storage LocalStorageService.add( LOCAL_STORAGE.ACCESS_TOKEN, - newUserData.getAccessToken, + newUserData.accessToken, ); } } diff --git a/typescript-practice/src/ts/views/index.ts b/typescript-practice/src/ts/views/index.ts index 8a45c8d..c3b08a6 100644 --- a/typescript-practice/src/ts/views/index.ts +++ b/typescript-practice/src/ts/views/index.ts @@ -1,9 +1,22 @@ +import { getSubdirectoryURL } from 'helpers/url'; +import LoginView from './loginView'; import RegisterView from './registerView'; +import { URL } from 'constants/config'; export default class View { - public registerView: RegisterView; + public registerView: RegisterView | null = null; + + public loginView: LoginView | null = null; constructor() { - this.registerView = new RegisterView(); + switch (getSubdirectoryURL()) { + case URL.LOGIN: + this.loginView = new LoginView(); + break; + case URL.REGISTER: + this.registerView = new RegisterView(); + break; + default: + } } } diff --git a/typescript-practice/src/ts/views/loginView.ts b/typescript-practice/src/ts/views/loginView.ts new file mode 100644 index 0000000..e05a001 --- /dev/null +++ b/typescript-practice/src/ts/views/loginView.ts @@ -0,0 +1,127 @@ +import AuthenticationView from './authenticationView'; +import { TypeToast, BTN_CONTENT } from '../constants/config'; +import User from 'models/user'; +import { DEFAULT_TITLE_ERROR_TOAST } from 'constants/messages/dialog'; +import { CustomError, TError } from 'global/types'; +import { redirectToLoginPage } from 'helpers/url'; +import { ERROR_CREDENTIAL } from 'constants/messages/form'; + +export default class LoginView extends AuthenticationView { + constructor() { + super(); + + this.initToast(); + this.initLoader(); + this.handleEventToast(); + } + + async loadPage(getInfoUserLogin: () => Promise) { + this.toggleLoaderSpinner(); + const user = await getInfoUserLogin(); + if (user) { + window.location.replace('/'); + } + this.toggleLoaderSpinner(); + } + + /** + * Implement error toast in site + * @param {string} content The content will show in error toast + */ + initErrorToast(error: TError | string): void { + const title = + typeof error === 'object' && error.title + ? error.title + : DEFAULT_TITLE_ERROR_TOAST; + + const content = + typeof error === 'object' && error.message + ? error.message + : (error as string); + + this.initToastContent(TypeToast.error, title, content, BTN_CONTENT.OK); + + if (this.toastDialog && this.toastBtn) { + // Show toast + this.toastDialog.showModal(); + + // Remove event for toast button + this.toastBtn.removeEventListener('click', redirectToLoginPage); + } + } + + /** + * Add event listener for form input + * @param {Function} validateUser The function need to be set event + */ + addHandlerForm( + loginUser: (email: string, password: string) => Promise, + ) { + if (this.formEl) + this.formEl.addEventListener('submit', (e: Event) => { + e.preventDefault(); + this.clearErrorMessage(); + this.submitForm(loginUser, e); + }); + } + + /** + * The action when submit form + * @param {Function} loginUser The function need to be set event + * * @param {event} event The event target + */ + async submitForm( + loginUser: (email: string, password: string) => Promise, + event: Event, + ) { + try { + // Load spinner + this.toggleLoaderSpinner(); + // Get data from form + const userInput = this.validateForm(event); + if (userInput) { + // Check user exist + const results = await loginUser(userInput.email, userInput.password); + if (results) { + window.location.replace('/'); + return; + } + throw new CustomError(ERROR_CREDENTIAL.title, ERROR_CREDENTIAL.message); + } + } catch (error) { + // Show toast error + this.initErrorToast(error as string | TError); + } + // Close spinner + this.toggleLoaderSpinner(); + } + + /** + * Get data from user input + * @returns {Object || null} Return object or null + */ + validateForm(event: Event): User | null { + if (event.target) { + const formData = new FormData(event.target as HTMLFormElement); + const email = formData.get('email') as string; + const password = formData.get('password') as string; + + // Validate user input + this.listError = []; // Reset list error + const emailValid = this.validateEmail(email); + const passwordValid = this.validatePassword(password); + + // Show error style + if (this.emailEl && this.inputPasswordEl) { + this.emailEl.classList.toggle('error-input', !emailValid); + this.inputPasswordEl.classList.toggle('error-input', !passwordValid); + if (emailValid && passwordValid) { + return new User(email, password); + } + } + } + + this.showError(this.listError); + return null; + } +} diff --git a/typescript-practice/src/ts/views/registerView.ts b/typescript-practice/src/ts/views/registerView.ts index c02e336..f60d587 100644 --- a/typescript-practice/src/ts/views/registerView.ts +++ b/typescript-practice/src/ts/views/registerView.ts @@ -1,7 +1,7 @@ import { TypeToast, BTN_CONTENT } from '../constants/config'; import AuthenticationView from './authenticationView'; import User from '../models/user'; -import { redirectToLoginPage } from '../helpers/redirect'; +import { redirectToLoginPage } from '../helpers/url'; import { DEFAULT_MESSAGE, DEFAULT_TITLE_ERROR_TOAST, @@ -148,7 +148,7 @@ export default class RegisterView extends AuthenticationView { // Save user if (user) { // Check user exist - const userExist = await checkExistUser(user.getEmail); + const userExist = await checkExistUser(user.email); if (userExist) { throw Error(USER_EXIST_ERROR); } else {