From b3ba1b9d00b2089c3d4a4429a29a4e0c71c08044 Mon Sep 17 00:00:00 2001 From: Max <133934232+Kleostro@users.noreply.github.com> Date: Fri, 3 May 2024 18:56:16 +0300 Subject: [PATCH] feat(RSS-ECOMM-2_14): set default address during registration (#131) * feat: add CountryChoice component * fix: postalCode validation * feat: update state * refactor: separate CountryChoice component into a module * feat: add settings address by default --- .../InputField/view/InputFieldView.ts | 11 +- .../CountryChoice/model/CountryChoiceModel.ts | 76 ++++++ .../model/countryChoiceModel.module.scss | 0 .../CountryChoice/view/CountryChoiceView.ts | 94 +++++++ .../view/countryChoiceView.module.scss | 109 ++++++++ .../model/InputFieldValidatorModel.ts | 19 +- .../view/registrationPageView.module.scss | 8 +- src/shared/Input/model/InputModel.ts | 4 +- src/shared/Input/tests/Input.spec.ts | 13 +- src/shared/Input/view/InputView.ts | 14 +- src/shared/Store/actions.ts | 14 +- src/shared/Store/observer.ts | 4 +- src/shared/Store/reducer.ts | 12 +- src/shared/constants/enums.ts | 177 ++++++++++-- src/shared/types/interfaces.ts | 2 +- .../model/RegistrationFormModel.ts | 94 ++++--- .../view/RegistrationFormView.ts | 252 +++++++++++++----- .../view/registrationForm.module.scss | 157 +++++------ 18 files changed, 788 insertions(+), 272 deletions(-) create mode 100644 src/features/CountryChoice/model/CountryChoiceModel.ts create mode 100644 src/features/CountryChoice/model/countryChoiceModel.module.scss create mode 100644 src/features/CountryChoice/view/CountryChoiceView.ts create mode 100644 src/features/CountryChoice/view/countryChoiceView.module.scss diff --git a/src/entities/InputField/view/InputFieldView.ts b/src/entities/InputField/view/InputFieldView.ts index a9d8ccb2..4765582a 100644 --- a/src/entities/InputField/view/InputFieldView.ts +++ b/src/entities/InputField/view/InputFieldView.ts @@ -49,16 +49,7 @@ class InputFieldView { } private createInput(inputParams: InputParams): InputModel { - const { autocomplete, id, lang, placeholder, type } = inputParams; - - this.input = new InputModel({ - autocomplete, - id, - lang: lang || '', - placeholder: placeholder || '', - type, - }); - + this.input = new InputModel(inputParams); return this.input; } diff --git a/src/features/CountryChoice/model/CountryChoiceModel.ts b/src/features/CountryChoice/model/CountryChoiceModel.ts new file mode 100644 index 00000000..da87cffd --- /dev/null +++ b/src/features/CountryChoice/model/CountryChoiceModel.ts @@ -0,0 +1,76 @@ +import getStore from '@/shared/Store/Store.ts'; +import { setBillingCountry, setShippingCountry } from '@/shared/Store/actions.ts'; +import observeStore, { selectBillingCountry, selectShippingCountry } from '@/shared/Store/observer.ts'; +import { EVENT_NAMES, REGISTRATION_FORM_BILLING_ADDRESS_COUNTRY_FIELD_PARAMS } from '@/shared/constants/enums.ts'; +import getCountryIndex from '@/shared/utils/getCountryIndex.ts'; + +import CountryChoiceView from '../view/CountryChoiceView.ts'; + +class CountryChoiceModel { + private view: CountryChoiceView; + + constructor(input: HTMLInputElement) { + this.view = new CountryChoiceView(input); + this.setCountryItemsHandlers(input); + this.setInputHandler(input); + + const action = + input.id === REGISTRATION_FORM_BILLING_ADDRESS_COUNTRY_FIELD_PARAMS.inputParams.id + ? selectBillingCountry + : selectShippingCountry; + + observeStore(action, () => { + const event = new Event(EVENT_NAMES.INPUT); + input.dispatchEvent(event); + }); + } + + private setCountryItemsHandlers(input: HTMLInputElement): boolean { + const inputHTML = input; + this.view.getCountryItems().forEach((countryItem) => { + const currentItem = countryItem; + currentItem.addEventListener(EVENT_NAMES.CLICK, () => { + if (currentItem.textContent) { + inputHTML.value = currentItem.textContent; + this.setCountryToStore(currentItem, inputHTML.id); + const event = new Event(EVENT_NAMES.INPUT); + inputHTML.dispatchEvent(event); + this.view.hideCountryChoice(); + } + }); + }); + return true; + } + + private setCountryToStore(element: HTMLDivElement | HTMLInputElement, key: string): boolean { + const currentCountryIndex = getCountryIndex( + element instanceof HTMLDivElement ? element.textContent || '' : element.value, + ); + + const action = + key === REGISTRATION_FORM_BILLING_ADDRESS_COUNTRY_FIELD_PARAMS.inputParams.id + ? setBillingCountry + : setShippingCountry; + getStore().dispatch(action(currentCountryIndex)); + return true; + } + + private setInputHandler(input: HTMLInputElement): boolean { + input.addEventListener(EVENT_NAMES.FOCUS, () => this.view.showCountryChoice()); + input.addEventListener(EVENT_NAMES.INPUT, () => { + this.view.switchVisibilityCountryItems(input); + this.setCountryToStore(input, input.id); + }); + return true; + } + + public getHTML(): HTMLDivElement { + return this.view.getHTML(); + } + + public getView(): CountryChoiceView { + return this.view; + } +} + +export default CountryChoiceModel; diff --git a/src/features/CountryChoice/model/countryChoiceModel.module.scss b/src/features/CountryChoice/model/countryChoiceModel.module.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/features/CountryChoice/view/CountryChoiceView.ts b/src/features/CountryChoice/view/CountryChoiceView.ts new file mode 100644 index 00000000..6a1d1e4d --- /dev/null +++ b/src/features/CountryChoice/view/CountryChoiceView.ts @@ -0,0 +1,94 @@ +import { COUNTRIES, EVENT_NAMES, TAG_NAMES } from '@/shared/constants/enums.ts'; +import createBaseElement from '@/shared/utils/createBaseElement.ts'; + +import styles from './countryChoiceView.module.scss'; + +class CountryChoiceView { + private countryChoice: HTMLDivElement; + + private countryDropList: HTMLDivElement; + + private countryItems: HTMLDivElement[] = []; + + constructor(input: HTMLInputElement) { + this.countryDropList = this.createCountryDropList(); + this.countryChoice = this.createHTML(); + + // TBD replace node document with some element because listener works two times (twice) + document.addEventListener(EVENT_NAMES.CLICK, (event) => { + if (!this.countryDropList.classList.contains(styles.hidden) && event.target !== input) { + this.hideCountryChoice(); + } + }); + } + + private createCountryDropList(): HTMLDivElement { + this.countryDropList = createBaseElement({ + cssClasses: [styles.countryDropList], + tag: TAG_NAMES.DIV, + }); + + Object.entries(COUNTRIES).forEach(([countryCode]) => + this.countryDropList.append(this.createCountryItem(countryCode)), + ); + + return this.countryDropList; + } + + private createCountryItem(countryCode: string): HTMLDivElement { + const countryItem = createBaseElement({ + cssClasses: [styles.countryItem], + innerContent: countryCode, + tag: TAG_NAMES.DIV, + }); + + this.countryItems.push(countryItem); + + return countryItem; + } + + private createHTML(): HTMLDivElement { + this.countryChoice = createBaseElement({ + cssClasses: [styles.countryChoice, styles.hidden], + tag: TAG_NAMES.DIV, + }); + + this.countryChoice.append(this.countryDropList); + + return this.countryChoice; + } + + public getCountryDropList(): HTMLDivElement { + return this.countryDropList; + } + + public getCountryItems(): HTMLDivElement[] { + return this.countryItems; + } + + public getHTML(): HTMLDivElement { + return this.countryChoice; + } + + public hideCountryChoice(): void { + this.countryChoice.classList.add(styles.hidden); + document.body.classList.remove(styles.stopScroll); + } + + public showCountryChoice(): void { + this.countryChoice.classList.remove(styles.hidden); + document.body.classList.add(styles.stopScroll); + } + + public switchVisibilityCountryItems(inputHTML: HTMLInputElement): boolean { + const filterValue = inputHTML.value.toLowerCase(); + this.countryItems.forEach((countryItem) => { + const itemValue = countryItem.textContent?.toLowerCase(); + countryItem.classList.toggle(styles.hidden, !itemValue?.includes(filterValue)); + }); + + return true; + } +} + +export default CountryChoiceView; diff --git a/src/features/CountryChoice/view/countryChoiceView.module.scss b/src/features/CountryChoice/view/countryChoiceView.module.scss new file mode 100644 index 00000000..3dcc49b3 --- /dev/null +++ b/src/features/CountryChoice/view/countryChoiceView.module.scss @@ -0,0 +1,109 @@ +.countryChoice { + position: sticky; + z-index: 5; + grid-column: 1; + grid-row: 4; + + @media (max-width: 768px) { + grid-column: 1; + grid-row: 5; + } +} + +.countryItem { + padding: calc(var(--extra-small-offset) / 4) calc(var(--extra-small-offset) / 2); + font: var(--regular-font); + letter-spacing: 1px; + text-align: end; + color: var(--noble-gray-800); + transition: + color 0.2s, + background-color 0.2s; + animation: show 0.5s ease-in forwards; + cursor: pointer; + + @media (hover: hover) { + &:hover { + color: var(--steam-green-800); + background-color: var(--noble-gray-200); + } + } + + @media (max-width: 768px) { + text-align: start; + } + + &.hidden { + animation: hide 0.5s ease-in forwards; + } +} + +@keyframes hide { + 0% { + opacity: 1; + visibility: visible; + } + + 50% { + opacity: 0; + visibility: hidden; + } + + 100% { + display: none; + } +} + +@keyframes show { + 0% { + display: block; + opacity: 0; + visibility: hidden; + } + + 100% { + opacity: 1; + visibility: visible; + } +} + +.hidden { + opacity: 0; + visibility: hidden; + pointer-events: none; +} + +.countryDropList { + position: absolute; + right: 0; + overflow-y: scroll; + border: 1px solid var(--noble-gray-300); + border-radius: var(--small-br); + width: 100%; + min-height: 200px; + max-height: 200px; + background-color: var(--white); + opacity: 1; + visibility: visible; + transition: + opacity 0.2s, + visibility 0.2s; + + &::-webkit-scrollbar { + width: 8px; + } + + &::-webkit-scrollbar-track { + background: var(--noble-gray-200); + } + + &::-webkit-scrollbar-thumb { + border-radius: var(--small-br); + background-color: var(--steam-green-800); + cursor: pointer; + } +} + +.stopScroll { + overflow-y: hidden; +} diff --git a/src/features/InputFieldValidator/model/InputFieldValidatorModel.ts b/src/features/InputFieldValidator/model/InputFieldValidatorModel.ts index fec768ad..228cd114 100644 --- a/src/features/InputFieldValidator/model/InputFieldValidatorModel.ts +++ b/src/features/InputFieldValidator/model/InputFieldValidatorModel.ts @@ -1,7 +1,7 @@ import type { InputFieldValidatorParams } from '@/shared/types/interfaces'; import getStore from '@/shared/Store/Store.ts'; -import { COUNTRIES } from '@/shared/constants/enums.ts'; +import { COUNTRIES, ERROR_MESSAGE, USER_POSTAL_CODE } from '@/shared/constants/enums.ts'; import { postcodeValidator } from 'postcode-validator'; class InputFieldValidatorModel { @@ -67,8 +67,7 @@ class InputFieldValidatorModel { private checkRequired(value: string): boolean | string { if (this.validParams.required && value.trim() === '') { - const errorMessage = 'Field is required'; - return errorMessage; + return ERROR_MESSAGE.REQUIRED_FIELD; } return true; @@ -95,8 +94,7 @@ class InputFieldValidatorModel { private checkValidCountry(value: string): boolean | string { if (this.validParams.validCountry) { if (!Object.keys(COUNTRIES).find((countryCode) => countryCode === value)) { - const errorMessage = 'Invalid country'; - return errorMessage; + return ERROR_MESSAGE.INVALID_COUNTRY; } } @@ -114,17 +112,16 @@ class InputFieldValidatorModel { private checkValidPostalCode(value: string): boolean | string { if (this.validParams.validPostalCode) { - const { registerFormCountry } = getStore().getState(); + const { billingCountry, shippingCountry } = getStore().getState(); + const currentCountry = this.validParams.key === USER_POSTAL_CODE.POSTAL_CODE ? shippingCountry : billingCountry; try { - const result = postcodeValidator(value, registerFormCountry); + const result = postcodeValidator(value, currentCountry); if (!result) { - const errorMessage = 'Invalid postal code'; - return errorMessage; + return ERROR_MESSAGE.INVALID_POSTAL_CODE; } } catch (error) { - const errorMessage = "Sorry, we don't deliver to your region yet"; - return errorMessage; + return ERROR_MESSAGE.WRONG_REGION; } } diff --git a/src/pages/RegistrationPage/view/registrationPageView.module.scss b/src/pages/RegistrationPage/view/registrationPageView.module.scss index d8a08795..6d6626e4 100644 --- a/src/pages/RegistrationPage/view/registrationPageView.module.scss +++ b/src/pages/RegistrationPage/view/registrationPageView.module.scss @@ -13,7 +13,6 @@ border-bottom: 10px solid var(--steam-green-800); border-radius: var(--medium-br); padding: calc(var(--large-offset) / 2) var(--small-offset); - max-width: 500px; background-color: var(--white); } @@ -24,13 +23,12 @@ justify-content: space-between; margin: 0 auto; margin-bottom: calc(var(--large-offset) / 2); - margin-left: 35%; - max-width: 160px; + width: max-content; &::after { content: ''; position: absolute; - right: calc(50% - 3px); + right: calc(36% - 3px); top: 50%; width: 3px; height: 16px; @@ -51,11 +49,13 @@ } .registerSpan { + margin-left: 10%; color: var(--steam-green-800); } .loginLink { position: relative; + margin-right: 20%; color: var(--noble-gray-800); transition: color 0.2s; diff --git a/src/shared/Input/model/InputModel.ts b/src/shared/Input/model/InputModel.ts index b6b52f70..9cefb036 100644 --- a/src/shared/Input/model/InputModel.ts +++ b/src/shared/Input/model/InputModel.ts @@ -1,3 +1,5 @@ +import type { InputParams } from '@/shared/types/interfaces.ts'; + import { IS_DISABLED } from '@/shared/constants/enums.ts'; import InputView from '../view/InputView.ts'; @@ -5,7 +7,7 @@ import InputView from '../view/InputView.ts'; class InputModel { private view: InputView; - constructor(attrs: Record) { + constructor(attrs: InputParams) { this.view = new InputView(attrs); } diff --git a/src/shared/Input/tests/Input.spec.ts b/src/shared/Input/tests/Input.spec.ts index 7ba608d8..df33a70e 100644 --- a/src/shared/Input/tests/Input.spec.ts +++ b/src/shared/Input/tests/Input.spec.ts @@ -1,9 +1,16 @@ +import type { InputParams } from '@/shared/types/interfaces.ts'; + import InputModel from '../model/InputModel.ts'; -const input = new InputModel({ - id: 'test', +const params: InputParams = { + autocomplete: 'on', + id: 'password', + lang: 'en', + placeholder: 'Enter password', type: 'password', -}); +}; + +const input = new InputModel(params); describe('Checking InputModel class', () => { it('should be defined', () => { expect(InputModel).toBeDefined(); diff --git a/src/shared/Input/view/InputView.ts b/src/shared/Input/view/InputView.ts index 73e27165..db7f0816 100644 --- a/src/shared/Input/view/InputView.ts +++ b/src/shared/Input/view/InputView.ts @@ -1,16 +1,24 @@ +import type { InputParams } from '@/shared/types/interfaces'; + import { TAG_NAMES } from '@/shared/constants/enums.ts'; import createBaseElement from '@/shared/utils/createBaseElement.ts'; class InputView { private input: HTMLInputElement; - constructor(attrs: Record) { + constructor(attrs: InputParams) { this.input = this.createHTML(attrs); } - private createHTML(attrs: Record): HTMLInputElement { + private createHTML(attrs: InputParams): HTMLInputElement { this.input = createBaseElement({ - attributes: attrs, + attributes: { + autocomplete: attrs.autocomplete, + id: attrs.id, + lang: attrs.lang || '', + placeholder: attrs.placeholder || '', + type: attrs.type, + }, tag: TAG_NAMES.INPUT, }); diff --git a/src/shared/Store/actions.ts b/src/shared/Store/actions.ts index 00ba075d..aeb61eb0 100644 --- a/src/shared/Store/actions.ts +++ b/src/shared/Store/actions.ts @@ -2,8 +2,9 @@ import type { UserInterface } from '../types/interfaces'; /* eslint-disable import/prefer-default-export */ const ACTION = { + SET_BILLING_COUNTRY: 'setBillingCountry', SET_CURRENT_USER: 'setCurrentUser', - SET_REGISTER_FORM_COUNTRY: 'setRegisterFormCountry', + SET_SHIPPING_COUNTRY: 'setShippingCountry', } as const; type ActionType = (typeof ACTION)[keyof typeof ACTION]; @@ -20,9 +21,12 @@ export const setCurrentUser = ( type: ACTION.SET_CURRENT_USER, }); -export const setRegisterFormCountry = ( - value: string, -): ActionWithPayload => ({ +export const setBillingCountry = (value: string): ActionWithPayload => ({ payload: value, - type: ACTION.SET_REGISTER_FORM_COUNTRY, + type: ACTION.SET_BILLING_COUNTRY, +}); + +export const setShippingCountry = (value: string): ActionWithPayload => ({ + payload: value, + type: ACTION.SET_SHIPPING_COUNTRY, }); diff --git a/src/shared/Store/observer.ts b/src/shared/Store/observer.ts index 6bcd3a74..0ff2f694 100644 --- a/src/shared/Store/observer.ts +++ b/src/shared/Store/observer.ts @@ -20,6 +20,8 @@ function observeStore(select: (state: State) => T, onChange: (selectedState: export const selectCurrentUser = (state: State): UserInterface | null => state.currentUser; -export const selectRegisterFormCountry = (state: State): string => state.registerFormCountry; +export const selectBillingCountry = (state: State): string => state.billingCountry; + +export const selectShippingCountry = (state: State): string => state.shippingCountry; export default observeStore; diff --git a/src/shared/Store/reducer.ts b/src/shared/Store/reducer.ts index 88e70809..0c1a4e01 100644 --- a/src/shared/Store/reducer.ts +++ b/src/shared/Store/reducer.ts @@ -4,8 +4,9 @@ import type * as actions from './actions.ts'; import type { Reducer } from './types.ts'; export interface State { + billingCountry: string; currentUser: UserInterface | null; - registerFormCountry: string; + shippingCountry: string; } type InferValueTypes = T extends { [key: string]: infer U } ? U : never; @@ -18,10 +19,15 @@ export const rootReducer: Reducer = (state: State, action: Action ...state, currentUser: action.payload, }; - case 'setRegisterFormCountry': + case 'setShippingCountry': return { ...state, - registerFormCountry: action.payload, + shippingCountry: action.payload, + }; + case 'setBillingCountry': + return { + ...state, + billingCountry: action.payload, }; default: return state; diff --git a/src/shared/constants/enums.ts b/src/shared/constants/enums.ts index 68ec09be..6f5c2d10 100644 --- a/src/shared/constants/enums.ts +++ b/src/shared/constants/enums.ts @@ -1,11 +1,13 @@ import type { State } from '../Store/reducer'; export const initialState: State = { + billingCountry: '', currentUser: null, - registerFormCountry: '', + shippingCountry: '', }; export const INPUT_TYPES = { + CHECK_BOX: 'checkbox', COLOR: 'color', DATE: 'date', EMAIL: 'email', @@ -27,11 +29,32 @@ export const IS_DISABLED = { ENABLED: false, } as const; +export const FORM_TEXT = { + DEFAULT_ADDRESS: 'Use as default address', +}; + export const MESSAGE_STATUS = { ERROR: 'error', SUCCESS: 'success', } as const; +export const ERROR_MESSAGE = { + INVALID_COUNTRY: 'Invalid country', + INVALID_POSTAL_CODE: 'Invalid postal code', + REQUIRED_FIELD: 'Field is required', + WRONG_REGION: "Sorry, we don't deliver to your region yet", +} as const; + +export const USER_COUNTRY_ADDRESS = { + BILLING: 'billingCountry', + SHIPPING: 'shippingCountry', +} as const; + +export const USER_POSTAL_CODE = { + BILLING_POSTAL_CODE: 'billing_PostalCode', + POSTAL_CODE: 'postalCode', +} as const; + export type MessageStatusType = (typeof MESSAGE_STATUS)[keyof typeof MESSAGE_STATUS]; export const SERVER_MESSAGE = { @@ -78,6 +101,7 @@ export const TAG_NAMES = { SELECT: 'select', SOURCE: 'source', SPAN: 'span', + STRONG: 'strong', SUMMARY: 'summary', SVG: 'svg', TABLE: 'table', @@ -168,7 +192,7 @@ export const LOGIN_FORM_INPUT_FIELD_PARAMS = [LOGIN_FORM_EMAIL_FIELD_PARAMS, LOG const LOGIN_FORM_EMAIL_FIELD_VALIDATE_PARAMS = { key: `${LOGIN_FORM_KEY}email`, notWhitespace: { - message: 'Email must not contain whitespaces', + message: 'Email must not contain white spaces', pattern: /^\S+$/, }, required: true, @@ -182,7 +206,7 @@ const LOGIN_FORM_PASSWORD_FIELD_VALIDATE_PARAMS = { key: `${LOGIN_FORM_KEY}password`, minLength: 8, notWhitespace: { - message: 'Password must not contain whitespaces', + message: 'Password must not contain white spaces', pattern: /^\S+$/, }, required: true, @@ -290,7 +314,7 @@ export const REGISTRATION_FORM_BIRTHDAY_FIELD_PARAMS = { }, } as const; -export const REGISTRATION_FORM_STREET_FIELD_PARAMS = { +export const REGISTRATION_FORM_SHIPPING_ADDRESS_STREET_FIELD_PARAMS = { inputParams: { autocomplete: 'off', id: 'address', @@ -303,7 +327,7 @@ export const REGISTRATION_FORM_STREET_FIELD_PARAMS = { }, } as const; -export const REGISTRATION_FORM_CITY_FIELD_PARAMS = { +export const REGISTRATION_FORM_SHIPPING_ADDRESS_CITY_FIELD_PARAMS = { inputParams: { autocomplete: 'off', id: 'city', @@ -316,20 +340,20 @@ export const REGISTRATION_FORM_CITY_FIELD_PARAMS = { }, } as const; -export const REGISTRATION_FORM_COUNTRY_FIELD_PARAMS = { +export const REGISTRATION_FORM_SHIPPING_ADDRESS_COUNTRY_FIELD_PARAMS = { inputParams: { autocomplete: 'off', - id: 'country', + id: 'shippingCountry', placeholder: 'Canada', type: 'text', }, labelParams: { - for: 'country', + for: 'shippingCountry', text: 'Country', }, } as const; -export const REGISTRATION_FORM_POSTAL_CODE_FIELD_PARAMS = { +export const REGISTRATION_FORM_SHIPPING_ADDRESS_POSTAL_CODE_FIELD_PARAMS = { inputParams: { autocomplete: 'off', id: 'postalCode', @@ -342,22 +366,78 @@ export const REGISTRATION_FORM_POSTAL_CODE_FIELD_PARAMS = { }, } as const; +export const REGISTRATION_FORM_BILLING_ADDRESS_STREET_FIELD_PARAMS = { + inputParams: { + autocomplete: 'off', + id: 'billing_address', + placeholder: '595 Hornby St. 5th Floor', + type: 'text', + }, + labelParams: { + for: 'billing_address', + text: 'Address', + }, +} as const; + +export const REGISTRATION_FORM_BILLING_ADDRESS_CITY_FIELD_PARAMS = { + inputParams: { + autocomplete: 'off', + id: 'billing_city', + placeholder: 'Vancouver', + type: 'text', + }, + labelParams: { + for: 'billing_city', + text: 'City', + }, +} as const; + +export const REGISTRATION_FORM_BILLING_ADDRESS_COUNTRY_FIELD_PARAMS = { + inputParams: { + autocomplete: 'off', + id: 'billing_country', + placeholder: 'Canada', + type: 'text', + }, + labelParams: { + for: 'billing_country', + text: 'Country', + }, +} as const; + +export const REGISTRATION_FORM_BILLING_ADDRESS_POSTAL_CODE_FIELD_PARAMS = { + inputParams: { + autocomplete: 'off', + id: 'billing_postalCode', + placeholder: 'A1B 2C3', + type: 'text', + }, + labelParams: { + for: 'billing_postalCode', + text: 'Postal code', + }, +} as const; + export const REGISTRATION_FORM_INPUT_FIELD_PARAMS = [ REGISTRATION_FORM_EMAIL_FIELD_PARAMS, REGISTRATION_FORM_PASSWORD_FIELD_PARAMS, REGISTRATION_FORM_FIRST_NAME_FIELD_PARAMS, REGISTRATION_FORM_LAST_NAME_FIELD_PARAMS, REGISTRATION_FORM_BIRTHDAY_FIELD_PARAMS, - REGISTRATION_FORM_STREET_FIELD_PARAMS, - REGISTRATION_FORM_CITY_FIELD_PARAMS, - REGISTRATION_FORM_COUNTRY_FIELD_PARAMS, - REGISTRATION_FORM_POSTAL_CODE_FIELD_PARAMS, + REGISTRATION_FORM_SHIPPING_ADDRESS_STREET_FIELD_PARAMS, + REGISTRATION_FORM_SHIPPING_ADDRESS_CITY_FIELD_PARAMS, + REGISTRATION_FORM_SHIPPING_ADDRESS_COUNTRY_FIELD_PARAMS, + REGISTRATION_FORM_SHIPPING_ADDRESS_POSTAL_CODE_FIELD_PARAMS, + REGISTRATION_FORM_BILLING_ADDRESS_STREET_FIELD_PARAMS, + REGISTRATION_FORM_BILLING_ADDRESS_CITY_FIELD_PARAMS, + REGISTRATION_FORM_BILLING_ADDRESS_COUNTRY_FIELD_PARAMS, + REGISTRATION_FORM_BILLING_ADDRESS_POSTAL_CODE_FIELD_PARAMS, ]; const REGISTRATION_FORM_EMAIL_FIELD_VALIDATE_PARAMS = { key: 'registration_email', notWhitespace: { - message: 'Email must not contain whitespaces', + message: 'Email must not contain white spaces', pattern: /^\S+$/, }, required: true, @@ -371,7 +451,7 @@ const REGISTRATION_FORM_PASSWORD_FIELD_VALIDATE_PARAMS = { key: 'registration_password', minLength: 8, notWhitespace: { - message: 'Password must not contain whitespaces', + message: 'Password must not contain white spaces', pattern: /^\S+$/, }, required: true, @@ -389,7 +469,7 @@ const REGISTRATION_FORM_FIRST_NAME_FIELD_VALIDATE_PARAMS = { pattern: /^[a-zA-Z]*$/, }, notWhitespace: { - message: 'First name must not contain whitespaces', + message: 'First name must not contain white spaces', pattern: /^\S+$/, }, required: true, @@ -403,7 +483,7 @@ const REGISTRATION_FORM_LAST_NAME_FIELD_VALIDATE_PARAMS = { pattern: /^[a-zA-Z]*$/, }, notWhitespace: { - message: 'Last name must not contain whitespaces', + message: 'Last name must not contain white spaces', pattern: /^\S+$/, }, required: true, @@ -420,13 +500,13 @@ const REGISTRATION_FORM_BIRTHDAY_FIELD_VALIDATE_PARAMS = { }, } as const; -export const REGISTRATION_FORM_STREET_FIELD_VALIDATE_PARAMS = { +export const REGISTRATION_FORM_SHIPPING_ADDRESS_STREET_FIELD_VALIDATE_PARAMS = { key: 'address', minLength: 1, required: true, }; -export const REGISTRATION_FORM_CITY_FIELD_VALIDATE_PARAMS = { +export const REGISTRATION_FORM_SHIPPING_ADDRESS_CITY_FIELD_VALIDATE_PARAMS = { key: 'city', minLength: 1, notSpecialSymbols: { @@ -437,30 +517,69 @@ export const REGISTRATION_FORM_CITY_FIELD_VALIDATE_PARAMS = { required: true, }; -export const REGISTRATION_FORM_COUNTRY_FIELD_VALIDATE_PARAMS = { - key: 'country', +export const REGISTRATION_FORM_SHIPPING_ADDRESS_COUNTRY_FIELD_VALIDATE_PARAMS = { + key: 'shippingCountry', required: true, validCountry: true, }; -export const REGISTRATION_FORM_POSTAL_CODE_FIELD_VALIDATE_PARAMS = { +export const REGISTRATION_FORM_SHIPPING_ADDRESS_POSTAL_CODE_FIELD_VALIDATE_PARAMS = { key: 'postalCode', required: true, validPostalCode: true, }; +export const REGISTRATION_FORM_BILLING_ADDRESS_STREET_FIELD_VALIDATE_PARAMS = { + key: 'billing_address', + minLength: 1, + required: true, +}; + +export const REGISTRATION_FORM_BILLING_ADDRESS_CITY_FIELD_VALIDATE_PARAMS = { + key: 'billing_city', + minLength: 1, + notSpecialSymbols: { + message: 'City must contain only letters', + pattern: /^[a-zA-Z]*$/, + }, + required: true, +}; + +export const REGISTRATION_FORM_BILLING_ADDRESS_COUNTRY_FIELD_VALIDATE_PARAMS = { + key: 'billing_country', + required: true, + validCountry: true, +}; + +export const REGISTRATION_FORM_BILLING_ADDRESS_POSTAL_CODE_FIELD_VALIDATE_PARAMS = { + key: 'billing_postalCode', + required: true, + validPostalCode: true, +}; + export const REGISTRATION_FORM_INPUT_FIELD_VALIDATION_PARAMS = [ REGISTRATION_FORM_EMAIL_FIELD_VALIDATE_PARAMS, REGISTRATION_FORM_PASSWORD_FIELD_VALIDATE_PARAMS, REGISTRATION_FORM_FIRST_NAME_FIELD_VALIDATE_PARAMS, REGISTRATION_FORM_LAST_NAME_FIELD_VALIDATE_PARAMS, REGISTRATION_FORM_BIRTHDAY_FIELD_VALIDATE_PARAMS, - REGISTRATION_FORM_STREET_FIELD_VALIDATE_PARAMS, - REGISTRATION_FORM_CITY_FIELD_VALIDATE_PARAMS, - REGISTRATION_FORM_COUNTRY_FIELD_VALIDATE_PARAMS, - REGISTRATION_FORM_POSTAL_CODE_FIELD_VALIDATE_PARAMS, + REGISTRATION_FORM_SHIPPING_ADDRESS_STREET_FIELD_VALIDATE_PARAMS, + REGISTRATION_FORM_SHIPPING_ADDRESS_CITY_FIELD_VALIDATE_PARAMS, + REGISTRATION_FORM_SHIPPING_ADDRESS_COUNTRY_FIELD_VALIDATE_PARAMS, + REGISTRATION_FORM_SHIPPING_ADDRESS_POSTAL_CODE_FIELD_VALIDATE_PARAMS, + REGISTRATION_FORM_BILLING_ADDRESS_STREET_FIELD_VALIDATE_PARAMS, + REGISTRATION_FORM_BILLING_ADDRESS_CITY_FIELD_VALIDATE_PARAMS, + REGISTRATION_FORM_BILLING_ADDRESS_COUNTRY_FIELD_VALIDATE_PARAMS, + REGISTRATION_FORM_BILLING_ADDRESS_POSTAL_CODE_FIELD_VALIDATE_PARAMS, ]; +export const REGISTRATION_FORM_TITLE_TEXT = { + BILLING_ADDRESS: 'Billing address', + CREDENTIALS: 'Credentials', + PERSONAL: 'Personal', + SHIPPING_ADDRESS: 'Shipping address', +} as const; + export const PASSWORD_TEXT = { HIDDEN: '********', SHOWN: 'Password123', @@ -682,3 +801,9 @@ export const SERVER_MESSAGE_ANIMATE_DETAILS = { easing: 'cubic-bezier(0, 0.2, 0.58, 0.7)', params: SERVER_MESSAGE_ANIMATE_PARAMS, }; + +export const CHECKBOX_PARAMS = { + AUTOCOMPLETE: 'off', + BILLING_ID: 'billingDefault', + SHIPPING_ID: 'shippingDefault', +} as const; diff --git a/src/shared/types/interfaces.ts b/src/shared/types/interfaces.ts index c9b4e3b6..bc4e68fd 100644 --- a/src/shared/types/interfaces.ts +++ b/src/shared/types/interfaces.ts @@ -41,7 +41,7 @@ export interface InputParams { id: string; lang?: string; placeholder: null | string; - type: 'color' | 'date' | 'email' | 'number' | 'password' | 'search' | 'tel' | 'text'; + type: 'checkbox' | 'color' | 'date' | 'email' | 'number' | 'password' | 'search' | 'tel' | 'text'; } export interface LabelParams { diff --git a/src/widgets/RegistrationForm/model/RegistrationFormModel.ts b/src/widgets/RegistrationForm/model/RegistrationFormModel.ts index ada64f84..69f1eb03 100644 --- a/src/widgets/RegistrationForm/model/RegistrationFormModel.ts +++ b/src/widgets/RegistrationForm/model/RegistrationFormModel.ts @@ -1,24 +1,22 @@ import type InputFieldModel from '@/entities/InputField/model/InputFieldModel.ts'; import type { UserRegisterData } from '@/shared/types/interfaces.ts'; +import CountryChoiceModel from '@/features/CountryChoice/model/CountryChoiceModel.ts'; import getCustomerModel from '@/shared/API/customer/model/CustomerModel.ts'; import serverMessageModel from '@/shared/ServerMessage/model/ServerMessageModel.ts'; import getStore from '@/shared/Store/Store.ts'; -import { setCurrentUser, setRegisterFormCountry } from '@/shared/Store/actions.ts'; -import observeStore, { selectRegisterFormCountry } from '@/shared/Store/observer.ts'; +import { setCurrentUser } from '@/shared/Store/actions.ts'; import { EVENT_NAMES, MESSAGE_STATUS, - REGISTRATION_FORM_COUNTRY_FIELD_VALIDATE_PARAMS, + REGISTRATION_FORM_BILLING_ADDRESS_COUNTRY_FIELD_PARAMS, REGISTRATION_FORM_KEY, - REGISTRATION_FORM_POSTAL_CODE_FIELD_VALIDATE_PARAMS, + REGISTRATION_FORM_SHIPPING_ADDRESS_COUNTRY_FIELD_PARAMS, SERVER_MESSAGE, } from '@/shared/constants/enums.ts'; -import getCountryIndex from '@/shared/utils/getCountryIndex.ts'; import isKeyOfUserData from '@/shared/utils/isKeyOfUserData.ts'; import RegistrationFormView from '../view/RegistrationFormView.ts'; -import REGISTRATION_FORM_STYLES from '../view/registrationForm.module.scss'; class RegisterFormModel { private inputFields: InputFieldModel[] = []; @@ -43,6 +41,46 @@ class RegisterFormModel { this.init(); } + private createBillingCountryChoice(): boolean { + const billingAddressInput = this.view + .getInputFields() + .find( + (inputField) => + inputField.getView().getInput().getHTML().id === + REGISTRATION_FORM_BILLING_ADDRESS_COUNTRY_FIELD_PARAMS.inputParams.id, + ) + ?.getView() + .getInput() + .getHTML(); + const billingAddressWrapper = this.view.getBillingAddressWrapper(); + + if (billingAddressInput) { + const countryChoiceModel = new CountryChoiceModel(billingAddressInput); + billingAddressWrapper.append(countryChoiceModel.getHTML()); + } + return true; + } + + private createShippingCountryChoice(): boolean { + const shippingAddressInput = this.view + .getInputFields() + .find( + (inputField) => + inputField.getView().getInput().getHTML().id === + REGISTRATION_FORM_SHIPPING_ADDRESS_COUNTRY_FIELD_PARAMS.inputParams.id, + ) + ?.getView() + .getInput() + .getHTML(); + const shippingAddressWrapper = this.view.getShippingAddressWrapper(); + + if (shippingAddressInput) { + const countryChoiceModel = new CountryChoiceModel(shippingAddressInput); + shippingAddressWrapper.append(countryChoiceModel.getHTML()); + } + return true; + } + private getFormData(): UserRegisterData { this.inputFields.forEach((inputField) => { const input = inputField.getView().getInput(); @@ -68,29 +106,12 @@ class RegisterFormModel { this.inputFields.forEach((inputField) => this.setInputFieldHandlers(inputField)); this.setPreventDefaultToForm(); this.setSubmitFormHandler(); + this.createBillingCountryChoice(); + this.createShippingCountryChoice(); return true; } - private setCountryItemsHandlers(input: HTMLInputElement): boolean { - const inputHTML = input; - this.view.getCountryItems().forEach((countryItem) => { - const currentItem = countryItem; - currentItem.addEventListener(EVENT_NAMES.CLICK, () => { - if (currentItem.textContent) { - inputHTML.value = currentItem.textContent; - const store = getStore(); - const currentCountryIndex = getCountryIndex(currentItem.textContent); - store.dispatch(setRegisterFormCountry(currentCountryIndex)); - const event = new Event(EVENT_NAMES.INPUT); - inputHTML.dispatchEvent(event); - this.view.hideCountryDropList(); - } - }); - }); - return true; - } - private setInputFieldHandlers(inputField: InputFieldModel): boolean { const inputHTML = inputField.getView().getInput().getHTML(); this.isValidInputFields[inputHTML.id] = false; @@ -98,29 +119,6 @@ class RegisterFormModel { this.isValidInputFields[inputHTML.id] = inputField.getIsValid(); this.switchSubmitFormButtonAccess(); }); - - if (inputHTML.id === REGISTRATION_FORM_COUNTRY_FIELD_VALIDATE_PARAMS.key) { - this.setCountryItemsHandlers(inputHTML); - inputHTML.addEventListener(EVENT_NAMES.FOCUS, () => this.view.showCountryDropList()); - inputHTML.addEventListener(EVENT_NAMES.INPUT, () => { - this.view.switchVisibilityCountryItems(inputHTML); - const currentCountryIndex = getCountryIndex(inputHTML.value); - getStore().dispatch(setRegisterFormCountry(currentCountryIndex)); - }); - document.addEventListener(EVENT_NAMES.CLICK, (event) => { - const countryDropList = this.view.getCountryDropList(); - if (!countryDropList.classList.contains(REGISTRATION_FORM_STYLES.hidden) && event.target !== inputHTML) { - this.view.hideCountryDropList(); - } - }); - } - - if (inputHTML.id === REGISTRATION_FORM_POSTAL_CODE_FIELD_VALIDATE_PARAMS.key) { - observeStore(selectRegisterFormCountry, () => { - const event = new Event(EVENT_NAMES.INPUT); - inputHTML.dispatchEvent(event); - }); - } return true; } diff --git a/src/widgets/RegistrationForm/view/RegistrationFormView.ts b/src/widgets/RegistrationForm/view/RegistrationFormView.ts index 70db316b..7e986faf 100644 --- a/src/widgets/RegistrationForm/view/RegistrationFormView.ts +++ b/src/widgets/RegistrationForm/view/RegistrationFormView.ts @@ -1,11 +1,30 @@ +import type { InputParams } from '@/shared/types/interfaces'; + import InputFieldModel from '@/entities/InputField/model/InputFieldModel.ts'; import ButtonModel from '@/shared/Button/model/ButtonModel.ts'; +import InputModel from '@/shared/Input/model/InputModel.ts'; import { BUTTON_TYPES, - COUNTRIES, + CHECKBOX_PARAMS, FORM_SUBMIT_BUTTON_TEXT, + FORM_TEXT, + INPUT_TYPES, + REGISTRATION_FORM_BILLING_ADDRESS_CITY_FIELD_PARAMS, + REGISTRATION_FORM_BILLING_ADDRESS_COUNTRY_FIELD_PARAMS, + REGISTRATION_FORM_BILLING_ADDRESS_POSTAL_CODE_FIELD_PARAMS, + REGISTRATION_FORM_BILLING_ADDRESS_STREET_FIELD_PARAMS, + REGISTRATION_FORM_BIRTHDAY_FIELD_PARAMS, + REGISTRATION_FORM_EMAIL_FIELD_PARAMS, + REGISTRATION_FORM_FIRST_NAME_FIELD_PARAMS, REGISTRATION_FORM_INPUT_FIELD_PARAMS, REGISTRATION_FORM_INPUT_FIELD_VALIDATION_PARAMS, + REGISTRATION_FORM_LAST_NAME_FIELD_PARAMS, + REGISTRATION_FORM_PASSWORD_FIELD_PARAMS, + REGISTRATION_FORM_SHIPPING_ADDRESS_CITY_FIELD_PARAMS, + REGISTRATION_FORM_SHIPPING_ADDRESS_COUNTRY_FIELD_PARAMS, + REGISTRATION_FORM_SHIPPING_ADDRESS_POSTAL_CODE_FIELD_PARAMS, + REGISTRATION_FORM_SHIPPING_ADDRESS_STREET_FIELD_PARAMS, + REGISTRATION_FORM_TITLE_TEXT, TAG_NAMES, } from '@/shared/constants/enums.ts'; import createBaseElement from '@/shared/utils/createBaseElement.ts'; @@ -13,58 +32,111 @@ import createBaseElement from '@/shared/utils/createBaseElement.ts'; import REGISTRATION_FORM_STYLES from './registrationForm.module.scss'; class RegistrationFormView { - private countryDropList: HTMLDivElement; + private billingAddressWrapper: HTMLDivElement; + + private checkboxDefaultBillingAddress: InputModel; - private countryItems: HTMLDivElement[] = []; + private checkboxDefaultShippingAddress: InputModel; - private countryWrapper: HTMLDivElement; + private credentialsWrapper: HTMLDivElement; private form: HTMLFormElement; private inputFields: InputFieldModel[] = []; + private personalDataWrapper: HTMLDivElement; + + private shippingAddressWrapper: HTMLDivElement; + private submitFormButton: ButtonModel; constructor() { this.inputFields = this.createInputFields(); + this.credentialsWrapper = this.createCredentialsWrapper(); + this.personalDataWrapper = this.createPersonalDataWrapper(); + this.checkboxDefaultShippingAddress = this.createCheckboxDefaultShippingAddress(); + this.shippingAddressWrapper = this.createShippingAddressWrapper(); + this.checkboxDefaultBillingAddress = this.createCheckboxDefaultBillingAddress(); + this.billingAddressWrapper = this.createBillingAddressWrapper(); this.submitFormButton = this.createSubmitFormButton(); - this.countryDropList = this.createCountryDropList(); - this.countryWrapper = this.createCountryWrapper(); this.form = this.createHTML(); } - private createCountryDropList(): HTMLDivElement { - this.countryDropList = createBaseElement({ - cssClasses: [REGISTRATION_FORM_STYLES.countryDropList, REGISTRATION_FORM_STYLES.hidden], - tag: TAG_NAMES.DIV, + private createBillingAddressWrapper(): HTMLDivElement { + const copyInputFields = this.inputFields; + const filteredInputFields = copyInputFields.filter( + (inputField) => + inputField.getView().getInput().getHTML().id === + REGISTRATION_FORM_BILLING_ADDRESS_STREET_FIELD_PARAMS.inputParams.id || + inputField.getView().getInput().getHTML().id === + REGISTRATION_FORM_BILLING_ADDRESS_CITY_FIELD_PARAMS.inputParams.id || + inputField.getView().getInput().getHTML().id === + REGISTRATION_FORM_BILLING_ADDRESS_COUNTRY_FIELD_PARAMS.inputParams.id || + inputField.getView().getInput().getHTML().id === + REGISTRATION_FORM_BILLING_ADDRESS_POSTAL_CODE_FIELD_PARAMS.inputParams.id, + ); + + this.billingAddressWrapper = this.createWrapperElement( + REGISTRATION_FORM_TITLE_TEXT.BILLING_ADDRESS, + [REGISTRATION_FORM_STYLES.billingAddressWrapper], + filteredInputFields, + ); + + const checkBoxLabel = createBaseElement({ + cssClasses: [REGISTRATION_FORM_STYLES.checkboxLabel], + tag: TAG_NAMES.LABEL, }); - Object.entries(COUNTRIES).forEach(([countryCode]) => { - this.countryDropList.append(this.createCountryItem(countryCode)); + const checkBoxText = createBaseElement({ + cssClasses: [REGISTRATION_FORM_STYLES.checkboxText], + innerContent: FORM_TEXT.DEFAULT_ADDRESS, + tag: TAG_NAMES.SPAN, }); - return this.countryDropList; - } + checkBoxLabel.append(checkBoxText, this.checkboxDefaultBillingAddress.getHTML()); - private createCountryItem(countryCode: string): HTMLDivElement { - const countryItem = createBaseElement({ - cssClasses: [REGISTRATION_FORM_STYLES.countryItem], - innerContent: countryCode, - tag: TAG_NAMES.DIV, - }); + this.billingAddressWrapper.append(checkBoxLabel); - this.countryItems.push(countryItem); + return this.billingAddressWrapper; + } - return countryItem; + private createCheckboxDefaultBillingAddress(): InputModel { + const checkboxParams: InputParams = { + autocomplete: CHECKBOX_PARAMS.AUTOCOMPLETE, + id: CHECKBOX_PARAMS.BILLING_ID, + placeholder: '', + type: INPUT_TYPES.CHECK_BOX, + }; + this.checkboxDefaultBillingAddress = new InputModel(checkboxParams); + return this.checkboxDefaultBillingAddress; } - private createCountryWrapper(): HTMLDivElement { - this.countryWrapper = createBaseElement({ - cssClasses: [REGISTRATION_FORM_STYLES.countryWrapper], - tag: TAG_NAMES.DIV, - }); + private createCheckboxDefaultShippingAddress(): InputModel { + const checkboxParams: InputParams = { + autocomplete: CHECKBOX_PARAMS.AUTOCOMPLETE, + id: CHECKBOX_PARAMS.SHIPPING_ID, + placeholder: '', + type: INPUT_TYPES.CHECK_BOX, + }; + this.checkboxDefaultShippingAddress = new InputModel(checkboxParams); + return this.checkboxDefaultShippingAddress; + } - return this.countryWrapper; + private createCredentialsWrapper(): HTMLDivElement { + const copyInputFields = this.inputFields; + const filteredInputFields = copyInputFields.filter( + (inputField) => + inputField.getView().getInput().getHTML().id === REGISTRATION_FORM_PASSWORD_FIELD_PARAMS.inputParams.id || + inputField.getView().getInput().getHTML().id === REGISTRATION_FORM_EMAIL_FIELD_PARAMS.inputParams.id, + ); + + this.credentialsWrapper = this.createWrapperElement( + REGISTRATION_FORM_TITLE_TEXT.CREDENTIALS, + [REGISTRATION_FORM_STYLES.credentialsWrapper], + filteredInputFields, + ); + + return this.credentialsWrapper; } private createHTML(): HTMLFormElement { @@ -73,18 +145,13 @@ class RegistrationFormView { tag: TAG_NAMES.FORM, }); - this.inputFields.forEach((inputField) => { - const inputFieldElement = inputField.getView().getHTML(); - - if (inputFieldElement instanceof HTMLLabelElement) { - this.form.append(inputFieldElement); - } else { - this.form.append(inputFieldElement.getHTML()); - } - }); - - this.countryWrapper.append(this.countryDropList); - this.form.append(this.countryWrapper, this.submitFormButton.getHTML()); + this.form.append( + this.credentialsWrapper, + this.personalDataWrapper, + this.shippingAddressWrapper, + this.billingAddressWrapper, + this.submitFormButton.getHTML(), + ); return this.form; } @@ -105,6 +172,62 @@ class RegistrationFormView { return this.inputFields; } + private createPersonalDataWrapper(): HTMLDivElement { + const copyInputFields = this.inputFields; + const filteredInputFields = copyInputFields.filter( + (inputField) => + inputField.getView().getInput().getHTML().id === REGISTRATION_FORM_FIRST_NAME_FIELD_PARAMS.inputParams.id || + inputField.getView().getInput().getHTML().id === REGISTRATION_FORM_LAST_NAME_FIELD_PARAMS.inputParams.id || + inputField.getView().getInput().getHTML().id === REGISTRATION_FORM_BIRTHDAY_FIELD_PARAMS.inputParams.id, + ); + + this.personalDataWrapper = this.createWrapperElement( + REGISTRATION_FORM_TITLE_TEXT.PERSONAL, + [REGISTRATION_FORM_STYLES.personalDataWrapper], + filteredInputFields, + ); + + return this.personalDataWrapper; + } + + private createShippingAddressWrapper(): HTMLDivElement { + const copyInputFields = this.inputFields; + const filteredInputFields = copyInputFields.filter( + (inputField) => + inputField.getView().getInput().getHTML().id === + REGISTRATION_FORM_SHIPPING_ADDRESS_STREET_FIELD_PARAMS.inputParams.id || + inputField.getView().getInput().getHTML().id === + REGISTRATION_FORM_SHIPPING_ADDRESS_CITY_FIELD_PARAMS.inputParams.id || + inputField.getView().getInput().getHTML().id === + REGISTRATION_FORM_SHIPPING_ADDRESS_COUNTRY_FIELD_PARAMS.inputParams.id || + inputField.getView().getInput().getHTML().id === + REGISTRATION_FORM_SHIPPING_ADDRESS_POSTAL_CODE_FIELD_PARAMS.inputParams.id, + ); + + this.shippingAddressWrapper = this.createWrapperElement( + REGISTRATION_FORM_TITLE_TEXT.SHIPPING_ADDRESS, + [REGISTRATION_FORM_STYLES.shippingAddressWrapper], + filteredInputFields, + ); + + const checkBoxLabel = createBaseElement({ + cssClasses: [REGISTRATION_FORM_STYLES.checkboxLabel], + tag: TAG_NAMES.LABEL, + }); + + const checkBoxText = createBaseElement({ + cssClasses: [REGISTRATION_FORM_STYLES.checkboxText], + innerContent: FORM_TEXT.DEFAULT_ADDRESS, + tag: TAG_NAMES.SPAN, + }); + + checkBoxLabel.append(checkBoxText, this.checkboxDefaultShippingAddress.getHTML()); + + this.shippingAddressWrapper.append(checkBoxLabel); + + return this.shippingAddressWrapper; + } + private createSubmitFormButton(): ButtonModel { this.submitFormButton = new ButtonModel({ attrs: { @@ -118,12 +241,31 @@ class RegistrationFormView { return this.submitFormButton; } - public getCountryDropList(): HTMLDivElement { - return this.countryDropList; + private createWrapperElement(title: string, cssClasses: string[], inputFields: InputFieldModel[]): HTMLDivElement { + const wrapperElement = createBaseElement({ + cssClasses, + tag: TAG_NAMES.DIV, + }); + const titleElement = createBaseElement({ + cssClasses: [REGISTRATION_FORM_STYLES.title], + innerContent: title, + tag: TAG_NAMES.H3, + }); + wrapperElement.append(titleElement); + + inputFields.forEach((inputField) => { + const inputFieldElement = inputField.getView().getHTML(); + if (inputFieldElement instanceof HTMLLabelElement) { + wrapperElement.append(inputFieldElement); + } else if (inputFieldElement instanceof InputModel) { + wrapperElement.append(inputFieldElement.getHTML()); + } + }); + return wrapperElement; } - public getCountryItems(): HTMLDivElement[] { - return this.countryItems; + public getBillingAddressWrapper(): HTMLDivElement { + return this.billingAddressWrapper; } public getHTML(): HTMLFormElement { @@ -134,26 +276,12 @@ class RegistrationFormView { return this.inputFields; } - public getSubmitFormButton(): ButtonModel { - return this.submitFormButton; - } - - public hideCountryDropList(): void { - this.countryDropList.classList.add(REGISTRATION_FORM_STYLES.hidden); + public getShippingAddressWrapper(): HTMLDivElement { + return this.shippingAddressWrapper; } - public showCountryDropList(): void { - this.countryDropList.classList.remove(REGISTRATION_FORM_STYLES.hidden); - } - - public switchVisibilityCountryItems(inputHTML: HTMLInputElement): boolean { - const filterValue = inputHTML.value.toLowerCase(); - this.countryItems.forEach((countryItem) => { - const itemValue = countryItem.textContent?.toLowerCase(); - countryItem.classList.toggle(REGISTRATION_FORM_STYLES.hidden, !itemValue?.includes(filterValue)); - }); - - return true; + public getSubmitFormButton(): ButtonModel { + return this.submitFormButton; } } diff --git a/src/widgets/RegistrationForm/view/registrationForm.module.scss b/src/widgets/RegistrationForm/view/registrationForm.module.scss index d5cb82d0..dcccbc5a 100644 --- a/src/widgets/RegistrationForm/view/registrationForm.module.scss +++ b/src/widgets/RegistrationForm/view/registrationForm.module.scss @@ -5,6 +5,34 @@ margin: 0 auto; gap: var(--extra-small-offset); + .checkboxLabel { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + cursor: pointer; + gap: calc(var(--extra-small-offset) / 4); + + input { + cursor: pointer; + } + + @media (hover: hover) { + &:hover { + .checkboxText { + color: var(--steam-green-800); + } + } + } + } + + .checkboxText { + font: var(--regular-font); + letter-spacing: 1px; + color: var(--noble-gray-800); + transition: color 0.2s; + } + label { position: relative; display: flex; @@ -118,7 +146,7 @@ button { grid-column: 2 span; - grid-row: 7; + grid-row: 5; border-radius: var(--small-br); padding: calc(var(--extra-small-offset) / 2) var(--small-offset); max-height: 40px; @@ -132,7 +160,6 @@ @media (max-width: 768px) { grid-column: 1; - grid-row: 11; } @media (hover: hover) { @@ -152,111 +179,53 @@ } } -.countryWrapper { - position: sticky; - grid-column: 2 span; - grid-row: 5; - - @media (max-width: 768px) { - grid-column: 1 span; - grid-row: 8; - } -} - -.countryDropList { - position: absolute; - right: 0; - overflow-y: scroll; - border: 1px solid var(--noble-gray-300); +.credentialsWrapper, +.personalDataWrapper, +.shippingAddressWrapper, +.billingAddressWrapper { + display: grid; + grid-template-columns: repeat(2, 1fr); + border: 2px solid var(--noble-gray-200); + border-right: 0; + border-left: 0; border-radius: var(--small-br); - width: 100%; - min-height: 200px; - max-height: 200px; - background-color: var(--white); - opacity: 1; - visibility: visible; - transition: - opacity 0.2s, - visibility 0.2s; - - &::-webkit-scrollbar { - width: 8px; - } - - &::-webkit-scrollbar-track { - background: var(--noble-gray-200); - } - - &::-webkit-scrollbar-thumb { - border-radius: var(--small-br); - background-color: var(--steam-green-800); - cursor: pointer; - } + padding: var(--extra-small-offset); + gap: var(--extra-small-offset); - @media (max-width: 768px) { - bottom: 0; - } -} + .title { + grid-column: 2 span; + font: var(--medium-font); + letter-spacing: 1px; + text-align: center; + color: var(--noble-gray-500); -.countryItem { - padding: calc(var(--extra-small-offset) / 4) calc(var(--extra-small-offset) / 2); - font: var(--regular-font); - letter-spacing: 1px; - text-align: end; - color: var(--noble-gray-800); - transition: - color 0.2s, - background-color 0.2s; - animation: show 0.5s ease-in forwards; - cursor: pointer; - - @media (hover: hover) { - &:hover { - color: var(--steam-green-800); - background-color: var(--noble-gray-200); + @media (max-width: 768px) { + grid-column: 1 span; } } @media (max-width: 768px) { - text-align: start; - } - - &.hidden { - animation: hide 0.5s ease-in forwards; + grid-column: 1 span; + grid-template-columns: repeat(1, 1fr); } } -@keyframes hide { - 0% { - opacity: 1; - visibility: visible; - } - - 50% { - opacity: 0; - visibility: hidden; - } - - 100% { - display: none; - } +.credentialsWrapper { + grid-column: 2 span; + grid-row: 1; } -@keyframes show { - 0% { - display: block; - opacity: 0; - visibility: hidden; - } +.personalDataWrapper { + grid-column: 2 span; + grid-row: 2; +} - 100% { - opacity: 1; - visibility: visible; - } +.shippingAddressWrapper { + grid-column: 2 span; + grid-row: 3; } -.hidden { - opacity: 0; - visibility: hidden; - pointer-events: none; +.billingAddressWrapper { + grid-column: 2 span; + grid-row: 4; }