From 35ff086052c9f6d7fc1ad3b802af6a1fa75f7831 Mon Sep 17 00:00:00 2001 From: Max <133934232+Kleostro@users.noreply.github.com> Date: Wed, 5 Jun 2024 09:11:51 +0300 Subject: [PATCH] feat: implement cooperation page --- src/app/App/model/AppModel.ts | 4 + src/app/styles/variables.scss | 4 +- .../CartPage/view/cartPageView.module.scss | 1 + .../model/CooperationPageModel.ts | 44 ++++++ .../view/CooperationPageView.ts | 136 ++++++++++++++++++ .../view/cooperationPageView.module.scss | 79 ++++++++++ src/shared/constants/pages.ts | 3 + .../types/validation/cooperationData.ts | 61 ++++++++ src/shared/utils/calcUserBirthDayRange.ts | 2 +- src/shared/utils/getCooperationData.ts | 9 ++ 10 files changed, 340 insertions(+), 3 deletions(-) create mode 100644 src/pages/CooperationPage/model/CooperationPageModel.ts create mode 100644 src/pages/CooperationPage/view/CooperationPageView.ts create mode 100644 src/pages/CooperationPage/view/cooperationPageView.module.scss create mode 100644 src/shared/types/validation/cooperationData.ts create mode 100644 src/shared/utils/getCooperationData.ts diff --git a/src/app/App/model/AppModel.ts b/src/app/App/model/AppModel.ts index 0adc04b7..22db35a7 100644 --- a/src/app/App/model/AppModel.ts +++ b/src/app/App/model/AppModel.ts @@ -37,6 +37,10 @@ class AppModel { const { default: CatalogPageModel } = await import('@/pages/CatalogPage/model/CatalogPageModel.ts'); return new CatalogPageModel(this.appView.getHTML()); }, + [PAGE_ID.COOPERATION_PAGE]: async (): Promise => { + const { default: CooperationPageModel } = await import('@/pages/CooperationPage/model/CooperationPageModel.ts'); + return new CooperationPageModel(this.appView.getHTML()); + }, [PAGE_ID.DEFAULT_PAGE]: async (): Promise => { const { default: MainPageModel } = await import('@/pages/MainPage/model/MainPageModel.ts'); return new MainPageModel(this.appView.getHTML()); diff --git a/src/app/styles/variables.scss b/src/app/styles/variables.scss index 7a947aab..fc6de1ac 100644 --- a/src/app/styles/variables.scss +++ b/src/app/styles/variables.scss @@ -40,8 +40,8 @@ --bold-font: 700 1rem 'Cerapro', sans-serif; // 16px --medium-bold-font: 700 1.125rem 'Cerapro', sans-serif; // 18px --extra-bold-font: 700 2.1875rem 'Cerapro', sans-serif; // 35px - --black-font: 900 2rem 'Cerapro', sans-serif; // 32px - --extra-black-font: 900 3.5rem 'Cerapro', sans-serif; // 70px + --black-font: 900 2.1875rem 'Cerapro', sans-serif; // 35px + --extra-black-font: 900 2.5rem 'Cerapro', sans-serif; // 40px body.light { // colors diff --git a/src/pages/CartPage/view/cartPageView.module.scss b/src/pages/CartPage/view/cartPageView.module.scss index 3f51d986..3374fdf1 100644 --- a/src/pages/CartPage/view/cartPageView.module.scss +++ b/src/pages/CartPage/view/cartPageView.module.scss @@ -170,6 +170,7 @@ $color: var(--steam-green-800); width: max-content; height: max-content; text-transform: none; + cursor: pointer; &::after { bottom: var(--five); diff --git a/src/pages/CooperationPage/model/CooperationPageModel.ts b/src/pages/CooperationPage/model/CooperationPageModel.ts new file mode 100644 index 00000000..170a83e8 --- /dev/null +++ b/src/pages/CooperationPage/model/CooperationPageModel.ts @@ -0,0 +1,44 @@ +import type { CooperationData } from '@/shared/types/validation/cooperationData.ts'; + +import getStore from '@/shared/Store/Store.ts'; +import { setCurrentPage } from '@/shared/Store/actions.ts'; +import observeStore, { selectCurrentLanguage } from '@/shared/Store/observer.ts'; +import { PAGE_ID } from '@/shared/constants/pages.ts'; +import isCooperationData from '@/shared/types/validation/cooperationData.ts'; +import getCooperationData from '@/shared/utils/getCooperationData.ts'; +import showErrorMessage from '@/shared/utils/userMessage.ts'; + +import CooperationPageView from '../view/CooperationPageView.ts'; + +class CooperationPageModel { + private view: CooperationPageView; + + constructor(parent: HTMLDivElement) { + this.view = new CooperationPageView(parent); + this.init(); + } + + private init(): void { + getCooperationData() + .then((data) => { + if (isCooperationData(data)) { + this.view.drawCooperationInfo(data); + this.observeState(data); + } + }) + .catch(showErrorMessage); + getStore().dispatch(setCurrentPage(PAGE_ID.COOPERATION_PAGE)); + } + + private observeState(data: CooperationData[]): void { + observeStore(selectCurrentLanguage, () => { + this.view.redrawCooperationInfo(data); + }); + } + + public getHTML(): HTMLDivElement { + return this.view.getHTML(); + } +} + +export default CooperationPageModel; diff --git a/src/pages/CooperationPage/view/CooperationPageView.ts b/src/pages/CooperationPage/view/CooperationPageView.ts new file mode 100644 index 00000000..0f5731ef --- /dev/null +++ b/src/pages/CooperationPage/view/CooperationPageView.ts @@ -0,0 +1,136 @@ +import type { CooperationData } from '@/shared/types/validation/cooperationData'; + +import getStore from '@/shared/Store/Store.ts'; +import createBaseElement from '@/shared/utils/createBaseElement.ts'; + +import styles from './cooperationPageView.module.scss'; + +class CooperationPageView { + private page: HTMLDivElement; + + private parent: HTMLDivElement; + + private wrapper: HTMLDivElement; + + constructor(parent: HTMLDivElement) { + this.parent = parent; + this.parent.innerHTML = ''; + this.wrapper = this.createCooperationWrapper(); + this.page = this.createHTML(); + window.scrollTo(0, 0); + } + + private createCooperationWrapper(): HTMLDivElement { + this.wrapper = createBaseElement({ + cssClasses: [styles.cooperationWrapper], + tag: 'div', + }); + + return this.wrapper; + } + + private createDescription(description: string): HTMLParagraphElement { + const descriptionElement = createBaseElement({ + cssClasses: [styles.cooperationDescription], + tag: 'p', + }); + descriptionElement.textContent = description; + return descriptionElement; + } + + private createHTML(): HTMLDivElement { + this.page = createBaseElement({ + cssClasses: [styles.cooperationPage], + tag: 'div', + }); + + this.page.append(this.wrapper); + this.parent.append(this.page); + + return this.page; + } + + private createItem(text: string): HTMLLIElement { + const listItem = createBaseElement({ + cssClasses: [styles.cooperationListItem], + innerContent: text, + tag: 'li', + }); + return listItem; + } + + private createItemList(): HTMLUListElement { + const itemList = createBaseElement({ + cssClasses: [styles.cooperationItemList], + tag: 'ul', + }); + return itemList; + } + + private createSubtitle(subtitle: string): HTMLHeadingElement { + const subtitleElement = createBaseElement({ + cssClasses: [styles.cooperationSubtitle], + tag: 'h2', + }); + subtitleElement.textContent = subtitle; + return subtitleElement; + } + + private createTitle(title: string): HTMLHeadingElement { + const titleElement = createBaseElement({ + cssClasses: [styles.cooperationTitle], + tag: 'h2', + }); + titleElement.textContent = title; + return titleElement; + } + + public drawCooperationInfo(data: CooperationData[]): void { + data.forEach((item) => { + const section = createBaseElement({ + cssClasses: [styles.cooperationSection], + tag: 'div', + }); + const currentTitle = item[getStore().getState().currentLanguage].title; + const currentDescription = item[getStore().getState().currentLanguage].description; + const currentSubtitle = item[getStore().getState().currentLanguage].subtitle; + const currentItems = item[getStore().getState().currentLanguage].items; + if (currentTitle) { + const title = this.createTitle(currentTitle); + section.append(title); + } + + if (currentDescription) { + const title = this.createDescription(currentDescription); + section.append(title); + } + + if (currentSubtitle) { + const title = this.createSubtitle(currentSubtitle); + section.append(title); + } + + if (currentItems) { + const createItemList = this.createItemList(); + currentItems.forEach((item) => { + const listItem = this.createItem(item.text); + createItemList.append(listItem); + }); + section.append(createItemList); + } + + this.wrapper.append(section); + }); + } + + public getHTML(): HTMLDivElement { + return this.page; + } + + public redrawCooperationInfo(data: CooperationData[]): void { + this.wrapper.innerHTML = ''; + this.drawCooperationInfo(data); + } +} + +export default CooperationPageView; diff --git a/src/pages/CooperationPage/view/cooperationPageView.module.scss b/src/pages/CooperationPage/view/cooperationPageView.module.scss new file mode 100644 index 00000000..146f8263 --- /dev/null +++ b/src/pages/CooperationPage/view/cooperationPageView.module.scss @@ -0,0 +1,79 @@ +.cooperationPage { + position: relative; + display: block; + padding: 0 var(--small-offset); + animation: show 0.2s ease-out forwards; +} + +@keyframes show { + 0% { + opacity: 0; + } + + 100% { + display: block; + opacity: 1; + } +} + +.cooperationWrapper { + display: flex; + flex-direction: column; + margin: 0 auto; + max-width: 80%; + gap: var(--extra-small-offset); + + @media (max-width: 768px) { + max-width: 100%; + } +} + +.cooperationSection { + display: flex; + flex-direction: column; + border-radius: var(--medium-br); + padding: var(--small-offset); + background-color: var(--steam-green-1000); + gap: var(--extra-small-offset); +} + +.cooperationTitle { + font: var(--medium-font); + letter-spacing: var(--one); + color: var(--steam-green-800); +} + +.cooperationSubtitle { + font: var(--medium-bold-font); + letter-spacing: var(--one); + color: var(--noble-gray-1000); +} + +.cooperationDescription { + font: var(--regular-font); + line-height: 170%; + letter-spacing: var(--one); + color: var(--noble-gray-800); +} + +.cooperationItemList { + display: flex; + flex-direction: column; + gap: var(--tiny-offset); +} + +.cooperationListItem { + position: relative; + margin-left: var(--extra-small-offset); + font: var(--regular-font); + letter-spacing: var(--one); + color: var(--noble-gray-700); + + &::after { + content: '✔'; + position: absolute; + left: -1.7rem; + top: 0; + padding: 0.15rem 0.3rem; + } +} diff --git a/src/shared/constants/pages.ts b/src/shared/constants/pages.ts index 5e5478ab..6309345b 100644 --- a/src/shared/constants/pages.ts +++ b/src/shared/constants/pages.ts @@ -8,6 +8,7 @@ export const PAGE_TITLE: Record> = { blog: 'Blog', cart: 'Cart', catalog: 'Catalog', + cooperation: 'Cooperation', login: 'Login', main: 'Main', product: 'Product', @@ -22,6 +23,7 @@ export const PAGE_TITLE: Record> = { blog: 'Блог', cart: 'Корзина', catalog: 'Каталог', + cooperation: 'Сотрудничество', login: 'Вход', main: 'Главная', product: 'Товар', @@ -125,6 +127,7 @@ export const PAGE_ID = { BLOG: 'blog', CART_PAGE: 'cart', CATALOG_PAGE: 'catalog', + COOPERATION_PAGE: 'cooperation', DEFAULT_PAGE: '', LOGIN_PAGE: 'login', MAIN_PAGE: 'main', diff --git a/src/shared/types/validation/cooperationData.ts b/src/shared/types/validation/cooperationData.ts new file mode 100644 index 00000000..9a12a689 --- /dev/null +++ b/src/shared/types/validation/cooperationData.ts @@ -0,0 +1,61 @@ +interface CooperationListItem { + text: string; +} + +interface CooperationItem { + description?: string; + items?: CooperationListItem[]; + subtitle?: string; + title?: string; +} + +export interface CooperationData { + en: CooperationItem; + ru: CooperationItem; +} + +const isCooperationItem = (data: unknown): data is CooperationItem => { + let result = true; + if (data === null || typeof data !== 'object') { + result = false; + return result; + } + + if ('description' in data && typeof data.description === 'string') { + result = true; + } + + if ('title' in data && typeof data.title === 'string') { + result = true; + } + + if ('subtitle' in data && typeof data.subtitle === 'string') { + result = true; + } + + if ('items' in data && Array.isArray(data.items)) { + data.items.forEach((item: CooperationListItem) => { + result = 'text' in item && typeof item.text === 'string'; + }); + } + + return result; +}; + +const isCooperationData = (data: unknown): data is CooperationData[] => { + let result = true; + if (!Array.isArray(data)) { + return false; + } + data.forEach((item: CooperationData) => { + if ('en' in item && 'ru' in item) { + result = isCooperationItem(item.en) && isCooperationItem(item.ru); + } else { + result = false; + } + }); + + return result; +}; + +export default isCooperationData; diff --git a/src/shared/utils/calcUserBirthDayRange.ts b/src/shared/utils/calcUserBirthDayRange.ts index 20d2ac27..c9c38bbb 100644 --- a/src/shared/utils/calcUserBirthDayRange.ts +++ b/src/shared/utils/calcUserBirthDayRange.ts @@ -4,7 +4,7 @@ const calcUserBirthDayRange = (birthDay: string): { end: string; start: string } const birthDate = new Date(birthDay); const start = new Date(birthDate.getFullYear(), birthDate.getMonth(), birthDate.getDate() - 3); - const end = new Date(birthDate.getFullYear(), birthDate.getMonth(), birthDate.getDate() + 4); + const end = new Date(birthDate.getFullYear(), birthDate.getMonth(), birthDate.getDate() + 3); if (start.getDate() < 1) { start.setMonth(start.getMonth() - 1); diff --git a/src/shared/utils/getCooperationData.ts b/src/shared/utils/getCooperationData.ts new file mode 100644 index 00000000..8fa9977a --- /dev/null +++ b/src/shared/utils/getCooperationData.ts @@ -0,0 +1,9 @@ +const COOPERATION_URL = 'https://raw.githubusercontent.com/stardustmeg/greenshop-db/main/cooperation/cooperation.json'; + +const getCooperationData = async (): Promise => { + const response = await fetch(COOPERATION_URL); + const data: unknown = await response.json(); + return data; +}; + +export default getCooperationData;