diff --git a/src/app/App/model/AppModel.ts b/src/app/App/model/AppModel.ts index 796877a6..0ae243c8 100644 --- a/src/app/App/model/AppModel.ts +++ b/src/app/App/model/AppModel.ts @@ -23,7 +23,7 @@ class AppModel { 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); + const notFoundPage = new NotFoundPageModel(root, this.router); const pages: Map = new Map( Object.entries({ [PAGES_IDS.DEFAULT_PAGE]: mainPage, diff --git a/src/app/Router/model/RouterModel.ts b/src/app/Router/model/RouterModel.ts index 841f3eff..ee27b8e5 100644 --- a/src/app/Router/model/RouterModel.ts +++ b/src/app/Router/model/RouterModel.ts @@ -19,7 +19,6 @@ class RouterModel { .slice(PATH_SEGMENTS_TO_KEEP + NEXT_SEGMENT) .join(DEFAULT_SEGMENT); this.handleRequest(currentPath); - this.eventMediator.notify(MEDIATOR_EVENTS.CHANGE_PAGE, currentPath.split(DEFAULT_SEGMENT).join()); }); window.addEventListener(EVENT_NAMES.POPSTATE, () => { @@ -35,10 +34,9 @@ class RouterModel { const pathParts = path.split(DEFAULT_SEGMENT); const hasRoute = this.pages.has(pathParts.join('')); if (!hasRoute) { - window.location.pathname = PAGES_IDS.NOT_FOUND_PAGE; + this.eventMediator.notify(MEDIATOR_EVENTS.CHANGE_PAGE, PAGES_IDS.NOT_FOUND_PAGE); return null; } - this.eventMediator.notify(MEDIATOR_EVENTS.CHANGE_PAGE, pathParts.join('')); return pathParts.join(''); } diff --git a/src/pages/NotFoundPage/model/NotFoundPageModel.ts b/src/pages/NotFoundPage/model/NotFoundPageModel.ts index f5b775ed..04a4c6f1 100644 --- a/src/pages/NotFoundPage/model/NotFoundPageModel.ts +++ b/src/pages/NotFoundPage/model/NotFoundPageModel.ts @@ -1,18 +1,38 @@ +import type RouterModel from '@/app/Router/model/RouterModel.ts'; import type { PageInterface } from '@/shared/types/interfaces.ts'; import EventMediatorModel from '@/shared/EventMediator/model/EventMediatorModel.ts'; -import { MEDIATOR_EVENTS, PAGES_IDS } from '@/shared/constants/enums.ts'; +import getStore from '@/shared/Store/Store.ts'; +import { EVENT_NAMES, MEDIATOR_EVENTS, PAGE_DESCRIPTION, PAGES_IDS } from '@/shared/constants/enums.ts'; import NotFoundPageView from '../view/NotFoundPageView.ts'; class NotFoundPageModel implements PageInterface { private eventMediator = EventMediatorModel.getInstance(); + private router: RouterModel; + private view: NotFoundPageView; - constructor(parent: HTMLDivElement) { + constructor(parent: HTMLDivElement, router: RouterModel) { this.view = new NotFoundPageView(parent); + this.router = router; + this.init(); + } + + private createPageDescription(): string { + const { currentUser } = getStore().getState(); + + const textDescription = currentUser + ? `Hi, ${currentUser.firstName}. ${PAGE_DESCRIPTION[404]}` + : PAGE_DESCRIPTION[404]; + return textDescription; + } + + private init(): boolean { this.subscribeToEventMediator(); + this.toMainButtonHandler(); + return true; } private subscribeToEventMediator(): void { @@ -22,6 +42,7 @@ class NotFoundPageModel implements PageInterface { private switchPageVisibility(route: unknown): boolean { if (route === PAGES_IDS.NOT_FOUND_PAGE) { this.view.show(); + this.view.setPageDescription(this.createPageDescription()); } else { this.view.hide(); return false; @@ -29,6 +50,12 @@ class NotFoundPageModel implements PageInterface { return true; } + private toMainButtonHandler(): boolean { + const toMainButton = this.view.getToMainButton().getHTML(); + toMainButton.addEventListener(EVENT_NAMES.CLICK, this.router.navigateTo.bind(this.router, PAGES_IDS.MAIN_PAGE)); + return true; + } + public getHTML(): HTMLDivElement { return this.view.getHTML(); } diff --git a/src/pages/NotFoundPage/view/NotFoundPageView.ts b/src/pages/NotFoundPage/view/NotFoundPageView.ts index 5ab7cfd6..c9eb826d 100644 --- a/src/pages/NotFoundPage/view/NotFoundPageView.ts +++ b/src/pages/NotFoundPage/view/NotFoundPageView.ts @@ -1,40 +1,98 @@ -import { TAG_NAMES } from '@/shared/constants/enums.ts'; +import ButtonModel from '@/shared/Button/model/ButtonModel.ts'; +import { SVG_DETAILS, TAG_NAMES } from '@/shared/constants/enums.ts'; import createBaseElement from '@/shared/utils/createBaseElement.ts'; +import createSVGUse from '@/shared/utils/createSVGUse.ts'; -import NOT_FOUND_PAGE_STYLES from './notFoundPageView.module.scss'; +import styles from './notFoundPageView.module.scss'; class NotFoundPageView { + private description: HTMLParagraphElement; + + private logo: HTMLDivElement; + private page: HTMLDivElement; private parent: HTMLDivElement; + private title: HTMLHeadingElement; + + private toMainButton: ButtonModel; + constructor(parent: HTMLDivElement) { this.parent = parent; + + this.logo = this.createPageLogo(); + this.title = this.createPageTitle(); + this.description = this.createPageDescription(); + this.toMainButton = this.createToMainButton(); + this.page = this.createHTML(); } private createHTML(): HTMLDivElement { this.page = createBaseElement({ - cssClasses: [NOT_FOUND_PAGE_STYLES.notFoundPage], + cssClasses: [styles.notFoundPage], tag: TAG_NAMES.DIV, }); + this.page.append(this.logo, this.title, this.description, this.toMainButton.getHTML()); this.parent.append(this.page); return this.page; } + private createPageDescription(): HTMLParagraphElement { + this.description = createBaseElement({ + cssClasses: [styles.pageDescription], + tag: TAG_NAMES.P, + }); + return this.description; + } + + private createPageLogo(): HTMLDivElement { + this.logo = createBaseElement({ cssClasses: [styles.pageLogo], tag: TAG_NAMES.DIV }); + const svg = document.createElementNS(SVG_DETAILS.SVG_URL, TAG_NAMES.SVG); + svg.append(createSVGUse(SVG_DETAILS.LOGO)); + + this.logo.append(svg); + + return this.logo; + } + + private createPageTitle(): HTMLHeadingElement { + this.title = createBaseElement({ + cssClasses: [styles.pageTitle], + innerContent: '404', + tag: TAG_NAMES.H1, + }); + return this.title; + } + + private createToMainButton(): ButtonModel { + this.toMainButton = new ButtonModel({ classes: [styles.toMainButton], text: 'Go Back' }); + return this.toMainButton; + } + public getHTML(): HTMLDivElement { return this.page; } + public getToMainButton(): ButtonModel { + return this.toMainButton; + } + public hide(): boolean { - this.page.classList.add(NOT_FOUND_PAGE_STYLES.notFoundPage_hidden); + this.page.classList.add(styles.notFoundPage_hidden); return true; } + public setPageDescription(text: string): HTMLParagraphElement { + this.description.innerText = text; + return this.description; + } + public show(): boolean { - this.page.classList.remove(NOT_FOUND_PAGE_STYLES.notFoundPage_hidden); + this.page.classList.remove(styles.notFoundPage_hidden); return true; } } diff --git a/src/pages/NotFoundPage/view/notFoundPageView.module.scss b/src/pages/NotFoundPage/view/notFoundPageView.module.scss index 0dd6bd7f..edb86785 100644 --- a/src/pages/NotFoundPage/view/notFoundPageView.module.scss +++ b/src/pages/NotFoundPage/view/notFoundPageView.module.scss @@ -1,9 +1,63 @@ .notFoundPage { position: relative; - display: block; - padding: 0 var(--small-offset); + display: flex; + flex-direction: column; + margin: 0 auto; + border-bottom: calc(var(--extra-small-offset) / 2) solid var(--steam-green-800); + padding: calc(var(--extra-large-offset) / 2) var(--extra-large-offset); + padding-bottom: calc(var(--extra-large-offset) / 2 + calc(var(--extra-small-offset) / 2)); + max-width: 500px; + background-color: var(--white); + gap: var(--medium-offset); &_hidden { display: none; } } + +.pageLogo { + display: flex; + margin: 0 auto; + + svg { + width: var(--large-offset); + height: var(--large-offset); + } +} + +.pageTitle { + display: flex; + margin: 0 auto; + font: var(--extra-medium-font); + letter-spacing: 1px; + color: var(--steam-green-800); +} + +.pageDescription { + font: var(--regular-font) '*' 1.5; + letter-spacing: 1px; + text-align: center; + color: var(--noble-gray-700); +} + +.toMainButton { + border-radius: var(--small-br); + padding: calc(var(--extra-small-offset) / 2) var(--small-offset); + font: var(--bold-font); + letter-spacing: 1px; + color: var(--white); + background-color: var(--steam-green-800); + transition: + color 0.2s, + background-color 0.2s; + + &:focus { + background-color: var(--steam-green-700); + } + + @media (hover: hover) { + &:hover { + background-color: var(--steam-green-700); + } + } +} diff --git a/src/shared/constants/enums.ts b/src/shared/constants/enums.ts index 8cf61d2b..ff9ff65e 100644 --- a/src/shared/constants/enums.ts +++ b/src/shared/constants/enums.ts @@ -190,6 +190,7 @@ export const FORM_SUBMIT_BUTTON_TEXT = { export const SVG_DETAILS = { CLOSE_EYE: 'closeEye', + LOGO: 'logo', OPEN_EYE: 'openEye', SVG_URL: 'http://www.w3.org/2000/svg', } as const; @@ -201,6 +202,7 @@ export const PAGE_LINK_TEXT = { } as const; export const PAGE_DESCRIPTION = { + 404: 'This is not the page you are looking for. Please go back to the main page.', LOGIN: 'Enter your email and password to login.', REGISTRATION: 'Enter your information to register.', } as const; diff --git a/src/shared/img/svg/logo.svg b/src/shared/img/svg/logo.svg new file mode 100644 index 00000000..1f6d90be --- /dev/null +++ b/src/shared/img/svg/logo.svg @@ -0,0 +1,3 @@ + + +