From 422dd431296094dd064f0e4fd6e296b2a02cc3ad Mon Sep 17 00:00:00 2001 From: Margarita Golubeva Date: Sat, 11 May 2024 10:03:44 +0300 Subject: [PATCH 1/2] feat: add a user notification on switching language --- .validate-branch-namerc.cjs | 3 +-- src/shared/constants/messages.ts | 2 ++ src/widgets/Header/model/HeaderModel.ts | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.validate-branch-namerc.cjs b/.validate-branch-namerc.cjs index c00c295e..3b33abf1 100644 --- a/.validate-branch-namerc.cjs +++ b/.validate-branch-namerc.cjs @@ -1,6 +1,5 @@ module.exports = { - pattern: - /^sprint-3|^(feat|fix|hotfix|chore|refactor|revert|docs|style|test|)\(RSS-ECOMM-\d{1}_\d{2}\)\/[a-z]+[a-zA-Z0-9]*$/, + pattern: /^(feat|fix|hotfix|chore|refactor|revert|docs|style|test|)\(RSS-ECOMM-\d{1}_\d{2}\)\/[a-z]+[a-zA-Z0-9]*$/, errorMsg: 'Please use correct branch name', }; diff --git a/src/shared/constants/messages.ts b/src/shared/constants/messages.ts index 37ed82f8..a276729b 100644 --- a/src/shared/constants/messages.ts +++ b/src/shared/constants/messages.ts @@ -10,6 +10,7 @@ export const SERVER_MESSAGE = { BAD_REQUEST: 'Sorry, something went wrong. Try again later.', INCORRECT_PASSWORD: 'Please, enter a correct password', INVALID_EMAIL: "User with this email doesn't exist. Please, register first", + LANGUAGE_CHANGED: 'Language preferences have been updated successfully', SUCCESSFUL_LOGIN: 'Enjoy shopping!', SUCCESSFUL_REGISTRATION: 'Your registration was successful', USER_EXISTS: 'User with this email already exists, please check your email', @@ -18,6 +19,7 @@ export const SERVER_MESSAGE = { BAD_REQUEST: 'Извините, что-то пошло не так. Попробуйте позже.', INCORRECT_PASSWORD: 'Пожалуйста, введите правильный пароль', INVALID_EMAIL: 'Пользователь с таким адресом не существует. Пожалуйста, сначала зарегистрируйтесь', + LANGUAGE_CHANGED: 'Настройки языка успешно обновлены', SUCCESSFUL_LOGIN: 'Приятных покупок!', SUCCESSFUL_REGISTRATION: 'Регистрация прошла успешно', USER_EXISTS: 'Пользователь с таким адресом уже существует, пожалуйста, проверьте свою почту', diff --git a/src/widgets/Header/model/HeaderModel.ts b/src/widgets/Header/model/HeaderModel.ts index fb1606a7..fa4e8579 100644 --- a/src/widgets/Header/model/HeaderModel.ts +++ b/src/widgets/Header/model/HeaderModel.ts @@ -109,8 +109,11 @@ class HeaderModel { switchLanguageButton.addEventListener('click', () => { const newLanguage = getStore().getState().currentLanguage === LANGUAGE_CHOICE.EN ? LANGUAGE_CHOICE.RU : LANGUAGE_CHOICE.EN; + + serverMessageModel.showServerMessage(SERVER_MESSAGE[newLanguage].LANGUAGE_CHANGED, MESSAGE_STATUS.SUCCESS); getStore().dispatch(setCurrentLanguage(newLanguage)); }); + return true; } From 9be3d0fa82a238386f4c7b2f3f20ddd93f628c3b Mon Sep 17 00:00:00 2001 From: Margarita Golubeva Date: Tue, 14 May 2024 02:07:19 +0300 Subject: [PATCH 2/2] feat: implement switching locale --- src/shared/constants/messages.ts | 12 ++ src/widgets/Header/model/HeaderModel.ts | 35 +++-- src/widgets/Header/view/HeaderView.ts | 55 +++++--- .../Header/view/headerView.module.scss | 123 ++++++++++++++---- 4 files changed, 174 insertions(+), 51 deletions(-) diff --git a/src/shared/constants/messages.ts b/src/shared/constants/messages.ts index a276729b..91dc3e69 100644 --- a/src/shared/constants/messages.ts +++ b/src/shared/constants/messages.ts @@ -26,6 +26,18 @@ export const SERVER_MESSAGE = { }, } as const; +export const SERVER_MESSAGE_KEYS = { + BAD_REQUEST: 'BAD_REQUEST', + INCORRECT_PASSWORD: 'INCORRECT_PASSWORD', + INVALID_EMAIL: 'INVALID_EMAIL', + LANGUAGE_CHANGED: 'LANGUAGE_CHANGED', + SUCCESSFUL_LOGIN: 'SUCCESSFUL_LOGIN', + SUCCESSFUL_REGISTRATION: 'SUCCESSFUL_REGISTRATION', + USER_EXISTS: 'USER_EXISTS', +} as const; + +export type ServerMessageKey = (typeof SERVER_MESSAGE_KEYS)[keyof typeof SERVER_MESSAGE_KEYS]; + export const ERROR_MESSAGE = { en: { INVALID_COUNTRY: 'Invalid country', diff --git a/src/widgets/Header/model/HeaderModel.ts b/src/widgets/Header/model/HeaderModel.ts index e7117b94..20a04949 100644 --- a/src/widgets/Header/model/HeaderModel.ts +++ b/src/widgets/Header/model/HeaderModel.ts @@ -1,13 +1,13 @@ import type RouterModel from '@/app/Router/model/RouterModel.ts'; import NavigationModel from '@/entities/Navigation/model/NavigationModel.ts'; -import getCustomerModel from '@/shared/API/customer/model/CustomerModel.ts'; +import getCustomerModel, { CustomerModel } from '@/shared/API/customer/model/CustomerModel.ts'; import serverMessageModel from '@/shared/ServerMessage/model/ServerMessageModel.ts'; import getStore from '@/shared/Store/Store.ts'; import { setCurrentLanguage, setCurrentUser } from '@/shared/Store/actions.ts'; import observeStore, { selectCurrentUser } from '@/shared/Store/observer.ts'; import { LANGUAGE_CHOICE } from '@/shared/constants/buttons.ts'; -import { MESSAGE_STATUS, SERVER_MESSAGE } from '@/shared/constants/messages.ts'; +import { MESSAGE_STATUS, SERVER_MESSAGE, SERVER_MESSAGE_KEYS } from '@/shared/constants/messages.ts'; import { PAGE_ID } from '@/shared/constants/pages.ts'; import HeaderView from '../view/HeaderView.ts'; @@ -66,7 +66,7 @@ class HeaderModel { this.setLogoutButtonHandler(); this.setCartLinkHandler(); this.setProfileLinkHandler(); - this.setChangeLanguageButtonHandler(); + this.setChangeLanguageCheckboxHandler(); return true; } @@ -108,14 +108,29 @@ class HeaderModel { return true; } - private setChangeLanguageButtonHandler(): boolean { - const switchLanguageButton = this.view.getSwitchLanguageButton().getHTML(); - switchLanguageButton.addEventListener('click', () => { - const newLanguage = - getStore().getState().currentLanguage === LANGUAGE_CHOICE.EN ? LANGUAGE_CHOICE.RU : LANGUAGE_CHOICE.EN; + private setChangeLanguageCheckboxHandler(): boolean { + const switchLanguageCheckbox = this.view.getSwitchLanguageCheckbox().getHTML(); + switchLanguageCheckbox.addEventListener('click', async () => { + const { currentUser } = getStore().getState(); - serverMessageModel.showServerMessage(SERVER_MESSAGE[newLanguage].LANGUAGE_CHANGED, MESSAGE_STATUS.SUCCESS); - getStore().dispatch(setCurrentLanguage(newLanguage)); + try { + if (currentUser) { + const newLanguage = currentUser.locale === LANGUAGE_CHOICE.EN ? LANGUAGE_CHOICE.RU : LANGUAGE_CHOICE.EN; + const newUser = await getCustomerModel().editCustomer( + [CustomerModel.actionSetLocale(newLanguage)], + currentUser, + ); + getStore().dispatch(setCurrentLanguage(newLanguage)); + serverMessageModel.showServerMessage(SERVER_MESSAGE_KEYS.LANGUAGE_CHANGED, MESSAGE_STATUS.SUCCESS); + getStore().dispatch(setCurrentUser(newUser)); + } + } catch { + // TBD Change to showError + serverMessageModel.showServerMessage( + SERVER_MESSAGE[getStore().getState().currentLanguage].BAD_REQUEST, + MESSAGE_STATUS.ERROR, + ); + } }); return true; diff --git a/src/widgets/Header/view/HeaderView.ts b/src/widgets/Header/view/HeaderView.ts index 53344bb6..d14e716a 100644 --- a/src/widgets/Header/view/HeaderView.ts +++ b/src/widgets/Header/view/HeaderView.ts @@ -1,16 +1,18 @@ +import type { LanguageChoiceType } from '@/shared/constants/buttons.ts'; + import ButtonModel from '@/shared/Button/model/ButtonModel.ts'; import InputModel from '@/shared/Input/model/InputModel.ts'; import LinkModel from '@/shared/Link/model/LinkModel.ts'; import getStore from '@/shared/Store/Store.ts'; import { switchAppTheme } from '@/shared/Store/actions.ts'; -import observeStore, { selectCurrentLanguage, selectCurrentPage, selectCurrentUser } from '@/shared/Store/observer.ts'; -import { BUTTON_TEXT, BUTTON_TEXT_KEYS } from '@/shared/constants/buttons.ts'; +import observeStore, { selectCurrentPage, selectCurrentUser } from '@/shared/Store/observer.ts'; +import { BUTTON_TEXT, BUTTON_TEXT_KEYS, LANGUAGE_CHOICE } from '@/shared/constants/buttons.ts'; import { AUTOCOMPLETE_OPTION } from '@/shared/constants/common.ts'; import { INPUT_TYPE } from '@/shared/constants/forms.ts'; +import { EMAIL_FIELD } from '@/shared/constants/forms/login/fieldParams.ts'; import { PAGE_ID } from '@/shared/constants/pages.ts'; import APP_THEME from '@/shared/constants/styles.ts'; import SVG_DETAILS from '@/shared/constants/svg.ts'; -import clearOutElement from '@/shared/utils/clearOutElement.ts'; import createBaseElement from '@/shared/utils/createBaseElement.ts'; import createSVGUse from '@/shared/utils/createSVGUse.ts'; import observeCurrentLanguage from '@/shared/utils/observeCurrentLanguage.ts'; @@ -28,7 +30,7 @@ class HeaderView { private navigationWrapper: HTMLDivElement; - private switchLanguageButton: ButtonModel; + private switchLanguageCheckbox: InputModel; private switchThemeCheckbox: InputModel; @@ -44,7 +46,7 @@ class HeaderView { this.toCartLink = this.createToCartLink(); this.toProfileLink = this.createToProfileLink(); this.switchThemeCheckbox = this.createSwitchThemeCheckbox(); - this.switchLanguageButton = this.createSwitchLanguageButton(); + this.switchLanguageCheckbox = this.createSwitchLanguageCheckbox(); this.navigationWrapper = this.createNavigationWrapper(); this.burgerButton = this.createBurgerButton(); this.wrapper = this.createWrapper(); @@ -104,9 +106,9 @@ class HeaderView { return this.header; } - private createLanguageButtonSVG(): SVGSVGElement { + private createLanguageSVG(lang: LanguageChoiceType): SVGSVGElement { const svg = document.createElementNS(SVG_DETAILS.SVG_URL, 'svg'); - svg.append(createSVGUse(SVG_DETAILS.SWITCH_LANGUAGE[getStore().getState().currentLanguage])); + svg.append(createSVGUse(lang)); return svg; } @@ -141,7 +143,7 @@ class HeaderView { tag: 'div', }); this.navigationWrapper.append( - this.switchLanguageButton.getHTML(), + this.createSwitchLanguageLabel(), this.logoutButton.getHTML(), this.toCartLink.getHTML(), this.toProfileLink.getHTML(), @@ -151,19 +153,36 @@ class HeaderView { return this.navigationWrapper; } - private createSwitchLanguageButton(): ButtonModel { - this.switchLanguageButton = new ButtonModel({ - classes: [styles.switchLanguageButton], + private createSwitchLanguageCheckbox(): InputModel { + this.switchLanguageCheckbox = new InputModel({ + autocomplete: EMAIL_FIELD.inputParams.autocomplete, + id: styles.switchLanguageLabel, + placeholder: '', + type: INPUT_TYPE.CHECK_BOX, }); + this.switchLanguageCheckbox.getHTML().classList.add(styles.switchLanguageCheckbox); + this.switchLanguageCheckbox.getHTML().checked = getStore().getState().currentUser?.locale === LANGUAGE_CHOICE.EN; - this.switchLanguageButton.getHTML().append(this.createLanguageButtonSVG()); + return this.switchLanguageCheckbox; + } - observeStore(selectCurrentLanguage, () => { - clearOutElement(this.switchLanguageButton.getHTML()); - this.switchLanguageButton.getHTML().append(this.createLanguageButtonSVG()); + private createSwitchLanguageLabel(): HTMLLabelElement { + const label = createBaseElement({ + attributes: { for: styles.switchLanguageLabel }, + cssClasses: [styles.switchLanguageLabel, styles.switchLanguageLabelAbs], + tag: 'label', }); + const span = createBaseElement({ + cssClasses: [styles.switchLanguageLabelSpan], + tag: 'span', + }); + const enSVG = this.createLanguageSVG(LANGUAGE_CHOICE.EN); + enSVG.classList.add(styles.enSVG); + const ruSVG = this.createLanguageSVG(LANGUAGE_CHOICE.RU); + ruSVG.classList.add(styles.ruSVG); - return this.switchLanguageButton; + label.append(enSVG, ruSVG, this.switchLanguageCheckbox.getHTML(), span); + return label; } private createSwitchThemeCheckbox(): InputModel { @@ -297,8 +316,8 @@ class HeaderView { return this.navigationWrapper; } - public getSwitchLanguageButton(): ButtonModel { - return this.switchLanguageButton; + public getSwitchLanguageCheckbox(): InputModel { + return this.switchLanguageCheckbox; } public getToCartLink(): LinkModel { diff --git a/src/widgets/Header/view/headerView.module.scss b/src/widgets/Header/view/headerView.module.scss index 57490c8c..3e6bd13b 100644 --- a/src/widgets/Header/view/headerView.module.scss +++ b/src/widgets/Header/view/headerView.module.scss @@ -45,29 +45,6 @@ } } -.switchLanguageButton { - display: flex; - align-items: center; - order: 3; - overflow: hidden; - border: 2px solid var(--noble-gray-300); - border-radius: 50%; - width: var(--small-offset); - height: var(--small-offset); - transition: border-color 0.2s; - - svg { - width: var(--small-offset); - height: var(--small-offset); - } - - @media (hover: hover) { - &:hover { - border-color: var(--steam-green-800); - } - } -} - .logo { order: 1; width: 40px; @@ -377,3 +354,103 @@ right: 5px; fill: var(--white); } + +.switchLanguageLabel { + position: relative; + display: inline-block; + order: 3; + width: var(--large-offset); + height: calc(calc(var(--small-offset) / 1.5) + var(--extra-small-offset)); + cursor: pointer; + + @media (max-width: 768px) { + width: var(--large-offset); + } + + &:disabled { + background-color: var(--noble-gray-300); + pointer-events: none; + } +} + +.switchLanguageCheckbox { + width: 0; + height: 0; + opacity: 1; + + &:checked { + + .switchLanguageLabelSpan { + background-color: #c4c4c4a8; + } + + + .switchLanguageLabelSpan::before { + background-color: #c4c4c4a8; + transform: translate(calc(var(--small-offset) + 3.5px), -50%); + } + + @media (max-width: 768px) { + + .switchLanguageLabelSpan::before { + transform: translate(calc(var(--small-offset) - 3.5px), -50%); + } + } + } +} + +.switchLanguageLabelSpan { + position: absolute; + border-radius: calc(var(--large-br) * 2); + background-color: #d8d8d85c; + transition: 0.3s cubic-bezier(0.8, 0.5, 0.2, 1.4); + cursor: pointer; + pointer-events: none; + inset: 0; + + &::before { + content: ''; + position: absolute; + left: 5px; + top: 50%; + bottom: 0; + z-index: 2; + border-radius: var(--large-br); + width: calc(var(--small-offset) / 1.5); + height: calc(var(--small-offset) / 1.5); + box-shadow: 0 1px 5px #353535; + background-color: #c4c4c4a8; + transform: translateY(-50%); + transition: 0.3s cubic-bezier(0.8, 0.5, 0.2, 1.4); + } +} + +.enSVG, +.ruSVG { + position: absolute; + top: 50%; + z-index: 1; + border-radius: 50%; + width: calc(var(--small-offset) / 1.5); + height: calc(var(--small-offset) / 1.5); + transform: translateY(-50%); + pointer-events: none; +} + +.enSVG { + left: 5px; + fill: var(--noble-gray-800); +} + +.ruSVG { + right: 5px; + fill: var(--white); +} + +@keyframes show { + 0% { + opacity: 0; + } + + 100% { + display: block; + opacity: 1; + } +}