Skip to content

Commit

Permalink
refactor(RSS-ECOMM-2_55): router component (#207)
Browse files Browse the repository at this point in the history
* refactor: rewrite Router component

* fix: navigation links

* fix: choosing country

* refactor: country lang choice based on input

* refactor: visual layout of pages

* fix: update store

* refactor: remove redundant code

* fix: styles

* feat: add init method for App component

* Apply suggestions from code review

Co-authored-by: Meg G. <[email protected]>

---------

Co-authored-by: Meg G. <[email protected]>
  • Loading branch information
Kleostro and stardustmeg authored May 9, 2024
1 parent bd96a1d commit c0e488a
Show file tree
Hide file tree
Showing 36 changed files with 186 additions and 412 deletions.
34 changes: 16 additions & 18 deletions src/app/App/model/AppModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,29 @@ class AppModel {
private router = new RouterModel();

constructor() {
this.router.setPages(this.initPages());
document.body.append(this.appView.getHTML());
this.appView.getHTML().insertAdjacentElement('beforebegin', new HeaderModel(this.router).getHTML());
this.appView.getHTML().insertAdjacentElement('afterend', new FooterModel(this.router).getHTML());
this.router.setRoutes(this.createRoutes());
}

private initPages(): Map<string, Page> {
const root = this.getHTML();
root.append(new HeaderModel(this.router).getHTML());
const loginPage = new LoginPageModel(root, this.router);
const mainPage = new MainPageModel(root, this.router);
const registrationPage = new RegistrationPageModel(root, this.router);
const notFoundPage = new NotFoundPageModel(root, this.router);
const pages: Map<string, Page> = new Map(
private createRoutes(): Map<string, () => Page> {
return new Map(
Object.entries({
[PAGE_ID.DEFAULT_PAGE]: mainPage,
[PAGE_ID.LOGIN_PAGE]: loginPage,
[PAGE_ID.MAIN_PAGE]: mainPage,
[PAGE_ID.NOT_FOUND_PAGE]: notFoundPage,
[PAGE_ID.REGISTRATION_PAGE]: registrationPage,
[PAGE_ID.DEFAULT_PAGE]: () => new MainPageModel(this.appView.getHTML()),
[PAGE_ID.LOGIN_PAGE]: () => new LoginPageModel(this.appView.getHTML(), this.router),
[PAGE_ID.MAIN_PAGE]: () => new MainPageModel(this.appView.getHTML()),
[PAGE_ID.NOT_FOUND_PAGE]: () => new NotFoundPageModel(this.appView.getHTML(), this.router),
[PAGE_ID.REGISTRATION_PAGE]: () => new RegistrationPageModel(this.appView.getHTML(), this.router),
}),
);
root.append(new FooterModel(this.router).getHTML());
return pages;
}

public getHTML(): HTMLDivElement {
return this.appView.getHTML();
public start(): boolean {
if (!this.appView.getHTML()) {
return false;
}
return true;
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/app/App/tests/App.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import AppModel from '../model/AppModel.ts';
const app = new AppModel();

describe('Checking AppModel class', () => {
it('the getHTML method should return HTMLDivElement', () => {
expect(app.getHTML()).toBeInstanceOf(HTMLDivElement);
it('application successfully created', () => {
expect(app.start()).toBe(true);
});
});
5 changes: 3 additions & 2 deletions src/app/App/view/appView.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
justify-content: center;
margin: 0 auto;
padding: 60px 0;
width: 100%;
min-height: 100vh;
min-height: calc(100vh - 141px);
max-width: 1440px;
}
55 changes: 23 additions & 32 deletions src/app/Router/model/RouterModel.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import type { Page } from '@/shared/types/common.ts';

import EventMediatorModel from '@/shared/EventMediator/model/EventMediatorModel.ts';
import MEDIATOR_EVENT from '@/shared/constants/events.ts';
import { PAGE_ID } from '@/shared/constants/pages.ts';

const DEFAULT_SEGMENT = import.meta.env.VITE_APP_DEFAULT_SEGMENT;
Expand All @@ -10,53 +8,46 @@ const PATH_SEGMENTS_TO_KEEP = import.meta.env.VITE_APP_PATH_SEGMENTS_TO_KEEP;
const PROJECT_TITLE = import.meta.env.VITE_APP_PROJECT_TITLE;

class RouterModel {
private eventMediator = EventMediatorModel.getInstance();

private pages: Map<string, Page> = new Map();
private routes: Map<string, () => Page> = new Map();

constructor() {
document.addEventListener('DOMContentLoaded', () => {
const currentPath = window.location.pathname
.split(DEFAULT_SEGMENT)
.slice(PATH_SEGMENTS_TO_KEEP + NEXT_SEGMENT)
.join(DEFAULT_SEGMENT);
this.handleRequest(currentPath);
this.navigateTo(currentPath);
});
}

private handleRequest(path: string): null | string {
const pathParts = path.split(DEFAULT_SEGMENT);
const hasRoute = this.pages.has(pathParts.join(''));
if (!hasRoute) {
document.title = `${PROJECT_TITLE} | ${PAGE_ID.NOT_FOUND_PAGE}`;
this.eventMediator.notify(MEDIATOR_EVENT.CHANGE_PAGE, PAGE_ID.NOT_FOUND_PAGE);
return null;
}
document.title = `${PROJECT_TITLE} | ${pathParts.join('')}`;
this.eventMediator.notify(MEDIATOR_EVENT.CHANGE_PAGE, pathParts.join(''));
return pathParts.join('');
private changeAppTitle(path: string, hasRoute: boolean): void {
const title = `${PROJECT_TITLE} | ${hasRoute ? path : PAGE_ID.NOT_FOUND_PAGE}`;
document.title = title;
}

public navigateTo(route: string): History {
if (this.pages.has(route)) {
const pathnameApp = window.location.pathname
.split(DEFAULT_SEGMENT)
.slice(NEXT_SEGMENT, PATH_SEGMENTS_TO_KEEP + NEXT_SEGMENT)
.join(DEFAULT_SEGMENT);
const url = `${pathnameApp}/${route}`;
const titleRoute = route === '' ? PAGE_ID.MAIN_PAGE : route;
document.title = `${PROJECT_TITLE} | ${titleRoute}`;
public navigateTo(path: string): void {
const pathnameApp = window.location.pathname
.split(DEFAULT_SEGMENT)
.slice(NEXT_SEGMENT, PATH_SEGMENTS_TO_KEEP + NEXT_SEGMENT)
.join(DEFAULT_SEGMENT);
const url = `${pathnameApp}/${path}`;
history.pushState(path, '', url);

history.pushState(route, '', url);
const pathParts = url.split(DEFAULT_SEGMENT);
const hasRoute = this.routes.has(pathParts[1]);
this.changeAppTitle(pathParts[1], hasRoute);

this.eventMediator.notify(MEDIATOR_EVENT.CHANGE_PAGE, route);
if (!hasRoute) {
this.routes.get(PAGE_ID.NOT_FOUND_PAGE)?.();
return;
}
return window.history;

this.routes.get(pathParts[1])?.();
}

public setPages(pages: Map<string, Page>): Map<string, Page> {
this.pages = pages;
return this.pages;
public setRoutes(routes: Map<string, () => Page>): Map<string, () => Page> {
this.routes = routes;
return this.routes;
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/app/styles/common.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
html,
body {
display: flex;
flex-direction: column;
align-items: center;
margin: 0 auto;
width: 100%;
min-width: 320px;
Expand Down
35 changes: 16 additions & 19 deletions src/entities/Navigation/model/NavigationModel.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import type RouterModel from '@/app/Router/model/RouterModel';

import EventMediatorModel from '@/shared/EventMediator/model/EventMediatorModel.ts';
import getStore from '@/shared/Store/Store.ts';
import observeStore, { selectCurrentUser } from '@/shared/Store/observer.ts';
import MEDIATOR_EVENT from '@/shared/constants/events.ts';
import observeStore, { selectCurrentPage, selectCurrentUser } from '@/shared/Store/observer.ts';
import { PAGE_ID } from '@/shared/constants/pages.ts';

import NavigationView from '../view/NavigationView.ts';

class NavigationModel {
private eventMediator = EventMediatorModel.getInstance();

private router: RouterModel;

private view = new NavigationView();
Expand All @@ -33,13 +29,14 @@ class NavigationModel {

private init(): boolean {
this.setNavigationLinksHandlers();
this.observeCurrentUser();
this.subscribeToEventMediator();
this.switchLinksState();
this.observeState();
return true;
}

private observeCurrentUser(): boolean {
observeStore(selectCurrentUser, () => this.checkCurrentUser.bind(this));
private observeState(): boolean {
observeStore(selectCurrentUser, () => this.checkCurrentUser());
observeStore(selectCurrentPage, () => this.switchLinksState());
return true;
}

Expand All @@ -55,16 +52,16 @@ class NavigationModel {
return true;
}

private subscribeToEventMediator(): boolean {
this.eventMediator.subscribe(MEDIATOR_EVENT.CHANGE_PAGE, (route) => {
const currentRoute = route === '' ? PAGE_ID.MAIN_PAGE : route;
const navigationLinks = this.view.getNavigationLinks();
const currentLink = navigationLinks.get(String(currentRoute));
navigationLinks.forEach((link) => link.setEnabled());
this.checkCurrentUser();
currentLink?.setDisabled();
this.view.switchActiveLink(String(currentRoute));
});
private switchLinksState(): boolean {
const { currentPage } = getStore().getState();
const currentPath = currentPage === '' ? PAGE_ID.MAIN_PAGE : currentPage;
const navigationLinks = this.view.getNavigationLinks();
const currentLink = navigationLinks.get(String(currentPath));
navigationLinks.forEach((link) => link.setEnabled());
this.checkCurrentUser();
currentLink?.setDisabled();
this.view.switchActiveLink(String(currentPath));

return true;
}

Expand Down
2 changes: 1 addition & 1 deletion src/features/CountryChoice/model/CountryChoiceModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class CountryChoiceModel {

private setCountryToStore(element: HTMLDivElement | HTMLInputElement, key: string): boolean {
const currentCountryIndex = getCountryIndex(
element instanceof HTMLDivElement ? formattedText(element.textContent ?? '') || '' : formattedText(element.value),
element instanceof HTMLDivElement ? formattedText(element.textContent ?? '') : formattedText(element.value),
);

const action = key === BILLING_ADDRESS_COUNTRY.inputParams.id ? setBillingCountry : setShippingCountry;
Expand Down
5 changes: 3 additions & 2 deletions src/features/InputFieldValidator/validators/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import getStore from '@/shared/Store/Store.ts';
import COUNTRIES_LIST from '@/shared/constants/countriesList.ts';
import { USER_POSTAL_CODE } from '@/shared/constants/forms.ts';
import { ERROR_MESSAGE } from '@/shared/constants/messages.ts';
import { checkInputLanguage } from '@/shared/utils/getCountryIndex.ts';
import { maxAgeMessage, maxLengthMessage, minAgeMessage, minLengthMessage } from '@/shared/utils/messageTemplate.ts';
import { postcodeValidator } from 'postcode-validator';

Expand Down Expand Up @@ -72,11 +73,11 @@ export const checkValidAge = (value: string, validParams: InputFieldValidatorPar
export const checkValidCountry = (value: string, validParams: InputFieldValidatorParams): boolean | string => {
if (validParams.validCountry) {
if (
!Object.keys(COUNTRIES_LIST[getStore().getState().currentLanguage]).find(
!Object.keys(COUNTRIES_LIST[checkInputLanguage(value)]).find(
(countryName) => countryName.toLowerCase() === value.toLowerCase(),
)
) {
return ERROR_MESSAGE[getStore().getState().currentLanguage].INVALID_COUNTRY;
return ERROR_MESSAGE[checkInputLanguage(value)].INVALID_COUNTRY;
}
}
return true;
Expand Down
2 changes: 1 addition & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import AppModel from '@/app/App/model/AppModel.ts';
import '@/styles.scss';

const myApp = new AppModel();
document.body.append(myApp.getHTML());
myApp.start();
41 changes: 15 additions & 26 deletions src/pages/LoginPage/model/LoginPageModel.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import type RouterModel from '@/app/Router/model/RouterModel.ts';
import type { Page } from '@/shared/types/common.ts';
import type { User } from '@/shared/types/user.ts';

import EventMediatorModel from '@/shared/EventMediator/model/EventMediatorModel.ts';
import getStore from '@/shared/Store/Store.ts';
import MEDIATOR_EVENT from '@/shared/constants/events.ts';
import { setCurrentPage } from '@/shared/Store/actions.ts';
import observeStore, { selectCurrentUser } from '@/shared/Store/observer.ts';
import { PAGE_ID, PAGE_LINK_TEXT, PAGE_LINK_TEXT_KEYS } from '@/shared/constants/pages.ts';
import observeCurrentLanguage from '@/shared/utils/observeCurrentLanguage.ts';
import LoginFormModel from '@/widgets/LoginForm/model/LoginFormModel.ts';

import LoginPageView from '../view/LoginPageView.ts';

class LoginPageModel implements Page {
private eventMediator = EventMediatorModel.getInstance();

private loginForm = new LoginFormModel();

private router: RouterModel;
Expand All @@ -25,20 +24,24 @@ class LoginPageModel implements Page {
this.init();
}

private checkAuthUser(): boolean {
if (!getStore().getState().currentUser) {
this.view.show();
this.loginForm.getFirstInputField().getView().getInput().getHTML().focus();
return false;
private checkAuthUser(): User | null {
const { currentUser } = getStore().getState();

if (currentUser) {
this.router.navigateTo(PAGE_ID.MAIN_PAGE);
return currentUser;
}
this.router.navigateTo(PAGE_ID.MAIN_PAGE);
return true;

return null;
}

private init(): boolean {
this.subscribeToEventMediator();
getStore().dispatch(setCurrentPage(PAGE_ID.LOGIN_PAGE));
this.checkAuthUser();
this.view.getAuthWrapper().append(this.loginForm.getHTML());
this.loginForm.getFirstInputField().getView().getInput().getHTML().focus();
this.setRegisterLinkHandler();
observeStore(selectCurrentUser, () => this.checkAuthUser());
return true;
}

Expand All @@ -59,20 +62,6 @@ class LoginPageModel implements Page {
toRegisterPageWrapper.append(registerLinkCopy);
}

private subscribeToEventMediator(): void {
this.eventMediator.subscribe(MEDIATOR_EVENT.CHANGE_PAGE, (route) => this.switchPageVisibility(route));
}

private switchPageVisibility(route: unknown): boolean {
if (route === PAGE_ID.LOGIN_PAGE) {
this.checkAuthUser();
} else {
this.view.hide();
return false;
}
return true;
}

public getHTML(): HTMLDivElement {
return this.view.getHTML();
}
Expand Down
14 changes: 1 addition & 13 deletions src/pages/LoginPage/view/LoginPageView.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import LinkModel from '@/shared/Link/model/LinkModel.ts';
import getStore from '@/shared/Store/Store.ts';
import { PAGE_TIMEOUT_DURATION } from '@/shared/constants/animations.ts';
import {
PAGE_ANSWER,
PAGE_ANSWER_KEYS,
Expand Down Expand Up @@ -36,6 +35,7 @@ class LoginPageView {

constructor(parent: HTMLDivElement) {
this.parent = parent;
this.parent.innerHTML = '';
this.toRegisterPageWrapper = this.createToRegisterPageWrapper();
this.loginSpan = this.createLoginSpan();
this.designElement = this.createDesignElement();
Expand Down Expand Up @@ -155,17 +155,5 @@ class LoginPageView {
public getToRegisterPageWrapper(): HTMLSpanElement {
return this.toRegisterPageWrapper;
}

public hide(): boolean {
this.page.classList.add(styles.loginPage_hidden);
return true;
}

public show(): boolean {
setTimeout(() => {
this.page.classList.remove(styles.loginPage_hidden);
}, PAGE_TIMEOUT_DURATION);
return true;
}
}
export default LoginPageView;
Loading

0 comments on commit c0e488a

Please sign in to comment.