feat(TEAMMSBMOB-25304): интеграция дпп со сторисами
This commit is contained in:
+2
-2
@@ -1,4 +1,4 @@
|
||||
NODE_ENV="development"
|
||||
ENABLE_MSW=true
|
||||
CDN_ENDPOINT="https://mobmsb.cdn.gpb.ru"
|
||||
STATIC_CONTENT_ENDPOINT="https://static.online.gpb.ru"
|
||||
CDN_ENDPOINT="http://localhost:8000/msb-host"
|
||||
STATIC_CONTENT_ENDPOINT="http://localhost:8000/msb-host"
|
||||
@@ -95,6 +95,7 @@ enum FEATURE_TOGGLE_NAMES {
|
||||
/** Тогл для отображения пункта меню добавления нового КА, редактирования и удаления. */
|
||||
COUNTER_PARTY_IBMSB = 'counterPartyIBMSB',
|
||||
PRODUCT_FOR_YOU = 'productForYouIBMSB',
|
||||
STORIES = 'mainpageStoriesDisplayIBMSB',
|
||||
}
|
||||
|
||||
export { FEATURE_TOGGLE_NAMES, type FeatureToggleData, type FeatureToggleItem, type FeatureToggleResponse };
|
||||
|
||||
@@ -115,7 +115,7 @@ interface PromoTab {
|
||||
|
||||
interface Promo {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
subTitle?: string;
|
||||
tag?: string;
|
||||
promoUrlCDN?: string;
|
||||
promoUrlStatic?: string;
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
const BUTTON_YM_CODE_SEPARATOR = '_';
|
||||
const CODE_SEPARATOR = '_';
|
||||
|
||||
export {BUTTON_YM_CODE_SEPARATOR};
|
||||
export { CODE_SEPARATOR };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TYPE_BANNER } from '../../model';
|
||||
import { BUTTON_YM_CODE_SEPARATOR } from '../constants';
|
||||
import { CODE_SEPARATOR } from '../constants';
|
||||
import type { DynamicBanner, SpaceWithBanners, GeneralBannerFile } from '../model';
|
||||
|
||||
/**
|
||||
@@ -15,8 +15,8 @@ const mapGeneralBannersFile = (
|
||||
.sort((prevBanner, currentBanner) => (prevBanner?.priority || 0) - (currentBanner?.priority || 0))
|
||||
.forEach(({ name, spaceCode, typeBanner, description, toggles, banner: bannerInfo }) => {
|
||||
const bannerInSpace = clonedSpaces?.[spaceCode]?.[typeBanner];
|
||||
|
||||
const ymElementName = `${typeBanner}_${name}`;
|
||||
const formatName = name.toLowerCase().trim().replace(/\s/g, CODE_SEPARATOR);
|
||||
const ymElementName = `${typeBanner}_${formatName}`;
|
||||
|
||||
const { miniature } = bannerInfo || {};
|
||||
|
||||
@@ -34,7 +34,8 @@ const mapGeneralBannersFile = (
|
||||
|
||||
if (bannerInSpace && bannerInSpace.length < bannerLimits[typeBanner]) {
|
||||
const formattedBanner: DynamicBanner = {
|
||||
id: `${name}_${bannerInfo?.title || ''}`,
|
||||
id: formatName,
|
||||
name: formatName,
|
||||
typeBanner,
|
||||
description,
|
||||
toggles,
|
||||
@@ -43,7 +44,8 @@ const mapGeneralBannersFile = (
|
||||
ymElementName,
|
||||
ymSpaceCode: `${spaceCode}_${typeBanner}`,
|
||||
title: bannerInfo.title || '',
|
||||
subtitle: bannerInfo?.subtitle,
|
||||
subTitle: bannerInfo?.subTitle,
|
||||
description: bannerInfo?.description,
|
||||
gradient: bannerInfo?.gradient,
|
||||
image: isToggleEnabled
|
||||
? `${process.env.CDN_ENDPOINT}${bannerInfo.imageUrl}`
|
||||
@@ -61,7 +63,7 @@ const mapGeneralBannersFile = (
|
||||
? `${process.env.CDN_ENDPOINT}${buttonIconUrl}`
|
||||
: `${process.env.STATIC_CONTENT_ENDPOINT}${buttonIconUrl}`,
|
||||
buttonLink,
|
||||
ymElementName: buttonText.trim().replace(/\s/g, BUTTON_YM_CODE_SEPARATOR),
|
||||
ymElementName: buttonText.trim().replace(/\s/g, CODE_SEPARATOR),
|
||||
ymSpaceCode: ymElementName,
|
||||
})),
|
||||
tabs: bannerInfo?.tabs,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { TYPE_BANNER } from '../../model';
|
||||
import { BUTTON_YM_CODE_SEPARATOR } from '../constants';
|
||||
import { CODE_SEPARATOR } from '../constants';
|
||||
import type { DynamicBanner, SpaceWithBanners, TargetBannersResponseDto, TargetBannerFile } from '../model';
|
||||
|
||||
/**
|
||||
@@ -56,7 +56,7 @@ const mapTargetBannersFile = (
|
||||
ymElementName,
|
||||
ymSpaceCode: `${spaceCode}_${typeBanner}`,
|
||||
title: bannerInFounded.title || '',
|
||||
subtitle: bannerInFounded?.subtitle,
|
||||
subTitle: bannerInFounded?.subTitle,
|
||||
gradient: bannerInFounded?.gradient,
|
||||
image: isToggleEnabled
|
||||
? `${process.env.CDN_ENDPOINT}${bannerInFounded.imageUrl}`
|
||||
@@ -76,7 +76,7 @@ const mapTargetBannersFile = (
|
||||
buttonLink,
|
||||
isEco: isECO,
|
||||
ymSpaceCode: ymElementName,
|
||||
ymElementName: buttonText.trim().replace(/\s/g, BUTTON_YM_CODE_SEPARATOR),
|
||||
ymElementName: buttonText.trim().replace(/\s/g, CODE_SEPARATOR),
|
||||
})
|
||||
),
|
||||
tabs: bannerInFounded?.tabs,
|
||||
|
||||
@@ -13,7 +13,8 @@ interface Banner {
|
||||
miniature?: Miniature;
|
||||
isGift?: boolean;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
subTitle?: string;
|
||||
description?: string;
|
||||
gradient: DYNAMIC_BANNERS_SALE_GRADIENTS;
|
||||
/** Используется для основного блока баннера. */
|
||||
imageUrl?: string;
|
||||
|
||||
@@ -13,7 +13,7 @@ interface BannerFile {
|
||||
miniature?: Miniature;
|
||||
isGift?: boolean;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
subTitle?: string;
|
||||
gradient: DYNAMIC_BANNERS_SALE_GRADIENTS;
|
||||
/** Используется для основного блока баннера. */
|
||||
imageUrl?: string;
|
||||
|
||||
@@ -2,8 +2,20 @@ import type { DYNAMIC_BANNERS_SALE_GRADIENTS } from '@msb/shared';
|
||||
import type { FEATURE_TOGGLE_NAMES } from '../../feature-toggles';
|
||||
import type { AdvInfo, BANNER_BUTTON_TYPE, BANNER_SPACE_CODE, BannerTab, TYPE_BANNER } from '../../model';
|
||||
|
||||
interface DynamicBannerButton {
|
||||
buttonType: BANNER_BUTTON_TYPE;
|
||||
buttonOrder: number;
|
||||
buttonText: string;
|
||||
buttonLink?: string;
|
||||
isEco?: boolean;
|
||||
buttonIcon?: string;
|
||||
ymSpaceCode: string;
|
||||
ymElementName: string;
|
||||
}
|
||||
|
||||
interface DynamicBanner {
|
||||
id: string;
|
||||
name?: string;
|
||||
typeBanner: TYPE_BANNER;
|
||||
description?: string;
|
||||
toggles?: FEATURE_TOGGLE_NAMES[];
|
||||
@@ -16,27 +28,19 @@ interface DynamicBanner {
|
||||
ymSpaceCode: string;
|
||||
ymElementName: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
subTitle?: string;
|
||||
description?: string;
|
||||
gradient: DYNAMIC_BANNERS_SALE_GRADIENTS;
|
||||
image?: string;
|
||||
isEco?: boolean;
|
||||
isGift?: boolean;
|
||||
pressLink?: string;
|
||||
adv?: AdvInfo;
|
||||
bannerButtons?: Array<{
|
||||
buttonType: BANNER_BUTTON_TYPE;
|
||||
buttonOrder: number;
|
||||
buttonText: string;
|
||||
buttonLink?: string;
|
||||
isEco?: boolean;
|
||||
buttonIcon?: string;
|
||||
ymSpaceCode: string;
|
||||
ymElementName: string;
|
||||
}>;
|
||||
bannerButtons?: DynamicBannerButton[];
|
||||
tabs?: BannerTab;
|
||||
};
|
||||
}
|
||||
|
||||
type SpaceWithBanners = Record<BANNER_SPACE_CODE, Partial<Record<TYPE_BANNER, DynamicBanner[]>>>;
|
||||
|
||||
export type { DynamicBanner, SpaceWithBanners };
|
||||
export type { DynamicBanner, DynamicBannerButton, SpaceWithBanners };
|
||||
|
||||
@@ -184,6 +184,10 @@ const FEATURE_TOGGLE_MOCK: FeatureToggleResponse = {
|
||||
featureCode: FEATURE_TOGGLE_NAMES.PRODUCT_FOR_YOU,
|
||||
isEnabled: true,
|
||||
},
|
||||
{
|
||||
featureCode: FEATURE_TOGGLE_NAMES.STORIES,
|
||||
isEnabled: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -32,26 +32,256 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "story_feed_accounts_1",
|
||||
"name": "ausn",
|
||||
"typeBanner": "storyFeed",
|
||||
"spaceCode": "accountsIB",
|
||||
"priority": 2,
|
||||
"description": "Story feed banner",
|
||||
"toggles": [],
|
||||
"imageUrl": "",
|
||||
"spaceCode": "mainPageIB",
|
||||
"priority": 1,
|
||||
"description": "",
|
||||
"toggles": ["mainpageStoriesDisplayIBMSB"],
|
||||
"banner": {
|
||||
"title": "Keep up with updates",
|
||||
"subtitle": "Stories from your bank",
|
||||
"gradient": "sale3",
|
||||
"imageUrl": "",
|
||||
"pressLink": "https://google.com",
|
||||
"adv": {
|
||||
"name": "Romashka",
|
||||
"inn": "1245124851",
|
||||
"erid": ""
|
||||
}
|
||||
"miniature": {
|
||||
"name": "АУСН\nДоступно",
|
||||
"gradient": "sale7",
|
||||
"imageUrl": "/stories/assets/miniature_1.webp"
|
||||
},
|
||||
"title": "АУСН онлайн",
|
||||
"description": "Автоматизированная упрощенная система налогообложения теперь доступна в Газпромбанке",
|
||||
"gradient": "sale7",
|
||||
"imageUrl": "/stories/assets/story_image_1.webp",
|
||||
"pressLink": "null",
|
||||
"bannerButtons": [
|
||||
{
|
||||
"buttonType": "prymary",
|
||||
"buttonOrder": 1,
|
||||
"buttonText": "Подробнее",
|
||||
"isECO": true,
|
||||
"buttonLink": "/debt",
|
||||
"buttonIconUrl": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Salary Project",
|
||||
"typeBanner": "storyFeed",
|
||||
"spaceCode": "mainPageIB",
|
||||
"priority": 2,
|
||||
"description": "",
|
||||
"toggles": ["mainpageStoriesDisplayIBMSB"],
|
||||
"banner": {
|
||||
"miniature": {
|
||||
"name": "Зарплатный\nпроект",
|
||||
"gradient": "sale6",
|
||||
"imageUrl": "/stories/assets/miniature_2.webp"
|
||||
},
|
||||
"title": "Зарплатный проект",
|
||||
"description": "Для сотрудников: 0₽ обслуживание, выпуск стикера, переводы по реквизитам и СБП и другие услуги.",
|
||||
"gradient": "sale7",
|
||||
"imageUrl": "/stories/assets/story_image_2.webp",
|
||||
"pressLink": "null",
|
||||
"bannerButtons": [
|
||||
{
|
||||
"buttonType": "prymary",
|
||||
"buttonOrder": 1,
|
||||
"buttonText": "Подробнее",
|
||||
"isECO": false,
|
||||
"buttonLink": "https://www.gazprombank.ru/business/salary-project/#first-step?utm_source=ib&utm_campaign=banner_april|d:dop|pn:salary-project|rt:web_bank|rk:old_client|ag:gpb&utm_medium=banner_stories_0404",
|
||||
"buttonIconUrl": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "foreign card",
|
||||
"typeBanner": "storyFeed",
|
||||
"spaceCode": "mainPageIB",
|
||||
"priority": 3,
|
||||
"description": "",
|
||||
"toggles": ["mainpageStoriesDisplayIBMSB"],
|
||||
"banner": {
|
||||
"miniature": {
|
||||
"name": "Карта\nбез границ",
|
||||
"gradient": "sale3",
|
||||
"imageUrl": "/stories/assets/miniature_card_without_borders.webp"
|
||||
},
|
||||
"title": "Карта без границ",
|
||||
"description": "Оплачивайте покупки за границей спокойно. Доставим бесплатно в любую точку России",
|
||||
"gradient": "sale7",
|
||||
"imageUrl": "/stories/assets/story_image_2.webp",
|
||||
"pressLink": "null",
|
||||
"adv": {
|
||||
"name": "ООО «Финкросс»",
|
||||
"inn": "9714051744",
|
||||
"erid": "2VtzqwET5ji"
|
||||
},
|
||||
"bannerButtons": [
|
||||
{
|
||||
"buttonType": "prymary",
|
||||
"buttonOrder": 1,
|
||||
"buttonText": "Попробовать",
|
||||
"isECO": false,
|
||||
"buttonLink": "https://www.fincross.ru/?utm_medium=banner&utm_source=dbo_msb_ib&utm_campaign=d:dcrm|rk:dc_fc|prm:dc_fc_banner",
|
||||
"buttonIconUrl": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "partner_check",
|
||||
"typeBanner": "storyFeed",
|
||||
"spaceCode": "mainPageIB",
|
||||
"priority": 4,
|
||||
"description": "",
|
||||
"toggles": ["mainpageStoriesDisplayIBMSB"],
|
||||
"banner": {
|
||||
"miniature": {
|
||||
"name": "Проверка контрагентов",
|
||||
"gradient": "sale7",
|
||||
"imageUrl": "/stories/assets/miniature_4.webp"
|
||||
},
|
||||
"title": "Проверка контрагентов",
|
||||
"description": "Проверяйте надёжность контрагентов \nдо заключения сделок",
|
||||
"gradient": "sale7",
|
||||
"imageUrl": "/stories/assets/story_image_4.webp",
|
||||
"pressLink": "null",
|
||||
"bannerButtons": [
|
||||
{
|
||||
"buttonType": "primary",
|
||||
"buttonOrder": 1,
|
||||
"buttonText": "Подключить",
|
||||
"isECO": false,
|
||||
"buttonLink": "/partner-check",
|
||||
"buttonIconUrl": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "payments_turkey",
|
||||
"typeBanner": "storyFeed",
|
||||
"spaceCode": "mainPageIB",
|
||||
"priority": 5,
|
||||
"description": "",
|
||||
"toggles": ["mainpageStoriesDisplayIBMSB"],
|
||||
"banner": {
|
||||
"miniature": {
|
||||
"name": "Платежи\nв Турцию 0,5%",
|
||||
"gradient": "sale7",
|
||||
"imageUrl": "/stories/assets/miniature_payments_turkey.webp"
|
||||
},
|
||||
"title": "Платежи в Турцию\nпо выгодной ставке",
|
||||
"description": "Переводите деньги в рублях и лирах с выгодной комиссией 0,5%, НДС\nне облагается.",
|
||||
"gradient": "sale7",
|
||||
"imageUrl": "/stories/assets/story_payments_turkey.webp",
|
||||
"pressLink": "null",
|
||||
"bannerButtons": [
|
||||
{
|
||||
"buttonType": "primary",
|
||||
"buttonOrder": 1,
|
||||
"buttonText": "Оставить заявку",
|
||||
"isECO": false,
|
||||
"buttonLink": "https://app.ab-payments.ru/auth",
|
||||
"buttonIconUrl": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "business_cards",
|
||||
"typeBanner": "storyFeed",
|
||||
"spaceCode": "mainPageIB",
|
||||
"priority": 6,
|
||||
"description": "",
|
||||
"toggles": ["mainpageStoriesDisplayIBMSB"],
|
||||
"banner": {
|
||||
"miniature": {
|
||||
"name": "Бизнес-карта\nОнлайн",
|
||||
"gradient": "sale5",
|
||||
"imageUrl": "/stories/assets/miniature_6.webp"
|
||||
},
|
||||
"title": "Бизнес-карта",
|
||||
"description": "Оформите карту для личных \nи деловых расходов",
|
||||
"gradient": "sale5",
|
||||
"imageUrl": "/stories/assets/story_image_6.webp",
|
||||
"pressLink": "null",
|
||||
"bannerButtons": [
|
||||
{
|
||||
"buttonType": "primary",
|
||||
"buttonOrder": 1,
|
||||
"buttonText": "Выпустить карту",
|
||||
"isECO": false,
|
||||
"buttonLink": "/business-cards/cards",
|
||||
"buttonIconUrl": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "bank_guarantees",
|
||||
"typeBanner": "storyFeed",
|
||||
"spaceCode": "mainPageIB",
|
||||
"priority": 7,
|
||||
"description": "",
|
||||
"toggles": ["mainpageStoriesDisplayIBMSB"],
|
||||
"banner": {
|
||||
"miniature": {
|
||||
"name": "Экспресс-гарантия",
|
||||
"gradient": "sale4",
|
||||
"imageUrl": "/stories/assets/miniature_7.webp"
|
||||
},
|
||||
"title": "Электронная\nбанковская гарантия",
|
||||
"description": "Оформите банковскую гарантию онлайн на сумму до 200 млн рублей. \nБез залога и поручителей",
|
||||
"gradient": "sale4",
|
||||
"imageUrl": "/stories/assets/story_image_6.webp",
|
||||
"pressLink": "null",
|
||||
"bannerButtons": [
|
||||
{
|
||||
"buttonType": "primary",
|
||||
"buttonOrder": 1,
|
||||
"buttonText": "Отправить заявку",
|
||||
"isECO": false,
|
||||
"buttonLink": "?productCode=ebg&sourcePage=stories&sourceSystem=MSB",
|
||||
"buttonIconUrl": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "catch_the_wave",
|
||||
"typeBanner": "storyFeed",
|
||||
"spaceCode": "mainPageIB",
|
||||
"priority": 8,
|
||||
"description": "",
|
||||
"toggles": ["mainpageStoriesDisplayIBMSB"],
|
||||
"banner": {
|
||||
"miniature": {
|
||||
"name": "Лови\nволну",
|
||||
"gradient": "sale9",
|
||||
"imageUrl": "/stories/assets/miniature_10.webp"
|
||||
},
|
||||
"title": "Ловите Волну\nв вашем бизнесе",
|
||||
"description": "Оплата с iPhone без карты и изменений\nв кассе — больше способов оплаты\nи роста продаж",
|
||||
"gradient": "sale9",
|
||||
"imageUrl": "/stories/assets/story_image_10.webp",
|
||||
"pressLink": "null",
|
||||
"bannerButtons": [
|
||||
{
|
||||
"buttonType": "primary",
|
||||
"buttonOrder": 1,
|
||||
"buttonText": "Подключить волну",
|
||||
"isECO": true,
|
||||
"buttonLink": "/paymenthub/lk",
|
||||
"buttonIconUrl": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": "product_for_you_mainpage_4",
|
||||
"typeBanner": "productForYou",
|
||||
@@ -181,4 +411,3 @@
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -299,10 +299,14 @@
|
||||
"featureCode": "depositQuestionnaireIBMSB",
|
||||
"isEnabled": false
|
||||
},
|
||||
{
|
||||
{
|
||||
"featureCode": "counterPartyIBMSB",
|
||||
"isEnabled": true
|
||||
},
|
||||
{
|
||||
"featureCode": "mainpageStoriesDisplayIBMSB",
|
||||
"isEnabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,13 @@ const config: IWebpackAppConfig = {
|
||||
},
|
||||
devServerOptions: {
|
||||
port: 8000,
|
||||
setupMiddlewares: (middlewares, devServer) => {
|
||||
if (devServer?.server) {
|
||||
devServer.server.setMaxListeners(20);
|
||||
}
|
||||
|
||||
return middlewares;
|
||||
},
|
||||
server: process.env.SECURE ? 'https' : 'http',
|
||||
proxy: [
|
||||
{
|
||||
|
||||
@@ -1,60 +1,78 @@
|
||||
const mockStories = [
|
||||
{
|
||||
id: '0001',
|
||||
image: '/stories/assets/miniature_1.webp',
|
||||
name: 'Счёт для бизнеса',
|
||||
ymCode: 'stories_slide_account',
|
||||
items: [
|
||||
/* eslint-disable sonarjs/no-duplicate-string */
|
||||
const mockBanners = {
|
||||
mainPageIB: {
|
||||
storyFeed: [
|
||||
{
|
||||
id: '1001',
|
||||
gradient: 'copper',
|
||||
image: '/stories/assets/story_image_1.webp',
|
||||
showTime: 10_000,
|
||||
content: {
|
||||
title: 'Счёт для бизнеса',
|
||||
description: '5 тарифов для выгодных расчётов в рублях и валюте',
|
||||
buttons: [
|
||||
id: 'ausn',
|
||||
name: 'ausn',
|
||||
typeBanner: 'storyFeed',
|
||||
description: '',
|
||||
toggles: ['mainpageStoriesDisplayIBMSB'],
|
||||
miniature: { name: 'АУСН Доступно', gradient: 'sale7', image: 'http://localhost:8000/msb-host/stories/assets/miniature_1.webp' },
|
||||
banner: {
|
||||
ymElementName: 'storyFeed_ausn',
|
||||
ymSpaceCode: 'mainPageIB_storyFeed',
|
||||
title: 'АУСН онлайн',
|
||||
description: 'Автоматизированная упрощённая система налогообложения теперь доступна в Газпромбанке',
|
||||
gradient: 'sale7',
|
||||
image: 'http://localhost:8000/msb-host/stories/assets/story_image_1.webp',
|
||||
pressLink: 'null',
|
||||
bannerButtons: [
|
||||
{
|
||||
link: '/open-account/new',
|
||||
linkType: 'eco',
|
||||
ymCode: 'stories_slide_account_open_account',
|
||||
text: 'Открыть счёт',
|
||||
buttonOrder: 1,
|
||||
buttonText: 'Кнопка 1',
|
||||
buttonType: 'prymary',
|
||||
isEco: true,
|
||||
buttonIcon: 'http://localhost:8000/msb-hostnull',
|
||||
buttonLink: '/debt',
|
||||
ymElementName: 'Подробнее',
|
||||
ymSpaceCode: 'storyFeed_ausn',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'salary_project',
|
||||
name: 'salary_project',
|
||||
typeBanner: 'storyFeed',
|
||||
description: '',
|
||||
toggles: ['mainpageStoriesDisplayIBMSB'],
|
||||
miniature: {
|
||||
name: 'Зарплатный проект',
|
||||
gradient: 'sale6',
|
||||
image: 'http://localhost:8000/msb-host/stories/assets/miniature_2.webp',
|
||||
},
|
||||
banner: {
|
||||
ymElementName: 'storyFeed_salary_project',
|
||||
ymSpaceCode: 'mainPageIB_storyFeed',
|
||||
title: 'Зарплатный проект',
|
||||
description: 'Для сотрудников: 0₽ обслуживание, выпуск стикера, переводы по реквизитам и СБП и другие услуги.',
|
||||
gradient: 'sale7',
|
||||
image: 'http://localhost:8000/msb-host/stories/assets/story_image_2.webp',
|
||||
pressLink: 'null',
|
||||
bannerButtons: [
|
||||
{
|
||||
link: 'https://www.gazprombank.ru/business/account-open/#tariffs',
|
||||
text: 'Подробнее',
|
||||
buttonOrder: 1,
|
||||
buttonText: 'Кнопка 2',
|
||||
buttonType: 'prymary',
|
||||
isEco: false,
|
||||
buttonIcon: 'http://localhost:8000/msb-hostnull',
|
||||
buttonLink:
|
||||
'https://www.gazprombank.ru/business/salary-project/#first-step?utm_source=ib&utm_campaign=banner_april|d:dop|pn:salary-project|rt:web_bank|rk:old_client|ag:gpb&utm_medium=banner_stories_0404',
|
||||
ymElementName: 'Подробнее',
|
||||
ymSpaceCode: 'storyFeed_salary_project',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '0002',
|
||||
image: '/stories/assets/miniature_2.webp',
|
||||
name: 'Депозиты для бизнеса',
|
||||
ymCode: 'stories_slide_deposit',
|
||||
items: [
|
||||
{
|
||||
id: '1002',
|
||||
gradient: 'sunsetSky',
|
||||
image: '/stories/assets/story_image_2.webp',
|
||||
showTime: 10_000,
|
||||
content: {
|
||||
title: 'Депозиты для бизнеса',
|
||||
description: 'Размещайте свободные средства на депозитах и получайте доход',
|
||||
buttons: [
|
||||
{
|
||||
link: '/deposits/treasury-deals',
|
||||
linkDocType: 'DEPOSIT',
|
||||
text: 'Открыть депозит',
|
||||
ymCode: 'stories_slide_deposit_open_deposit',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export { mockStories };
|
||||
const mockBannersEmptyState = {
|
||||
mainPageIB: {
|
||||
storyFeed: [],
|
||||
},
|
||||
};
|
||||
|
||||
export { mockBanners, mockBannersEmptyState };
|
||||
|
||||
@@ -1,29 +1,26 @@
|
||||
/* eslint-disable jest/no-mocks-import */
|
||||
import '@testing-library/jest-dom';
|
||||
import { render } from '@msb/shared/lib/tests/customRender';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import { mockStories } from '../__mocks__/mockStories';
|
||||
import { mockBanners, mockBannersEmptyState } from '../__mocks__/mockStories';
|
||||
import { StoriesBlock } from '../ui/StoriesBlock';
|
||||
|
||||
const mockWindowOpen = jest.fn();
|
||||
|
||||
const mockUseStories = jest.fn();
|
||||
|
||||
jest.mock('../models', () => ({
|
||||
__esModule: true,
|
||||
useStories: () => mockUseStories(),
|
||||
}));
|
||||
const mockUseBanners = jest.fn(() => ({ isDynamicBannersLoading: false, banners: mockBanners }));
|
||||
|
||||
const featureTogglesMock = {
|
||||
features: [],
|
||||
features: [{ featureCode: 'mainpageStoriesDisplayIBMSB', isEnabled: true }],
|
||||
error: null,
|
||||
};
|
||||
|
||||
const useFeatureTogglesMock = jest.fn(() => ({ isEnabled: true }));
|
||||
|
||||
const useAppContextMock = jest.fn(() => ({
|
||||
const useAppContextMock = jest.fn();
|
||||
|
||||
useAppContextMock.mockImplementation(() => ({
|
||||
organizations: [],
|
||||
userProfile: {},
|
||||
...mockUseBanners(),
|
||||
isUserProfileError: false,
|
||||
refetchUserProfile: jest.fn(),
|
||||
organizationsError: null,
|
||||
@@ -69,7 +66,7 @@ describe('StoriesBlock', () => {
|
||||
});
|
||||
|
||||
test('должен показывать скелетоны до того как загрузятся данные', () => {
|
||||
mockUseStories.mockReturnValue({ isLoading: true, stories: [] });
|
||||
mockUseBanners.mockReturnValue({ isDynamicBannersLoading: true, banners: mockBannersEmptyState });
|
||||
|
||||
render(<StoriesBlock />);
|
||||
|
||||
@@ -77,41 +74,40 @@ describe('StoriesBlock', () => {
|
||||
});
|
||||
|
||||
test('должен отображать данные и скрывать скелетоны когда загрузка завершена', () => {
|
||||
mockUseStories.mockReturnValue({ isLoading: false, stories: mockStories });
|
||||
mockUseBanners.mockReturnValue({ isDynamicBannersLoading: false, banners: mockBanners });
|
||||
|
||||
render(<StoriesBlock />);
|
||||
|
||||
mockStories.forEach(story => {
|
||||
expect(screen.getByText(story.name)).toBeInTheDocument();
|
||||
mockBanners.mainPageIB.storyFeed.forEach(story => {
|
||||
expect(screen.getByText(story.miniature.name)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.queryAllByTestId('skeleton')).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('должна открыться карусель с историями при клике на элемент в ленте', () => {
|
||||
const firstStoryContent = mockStories[0].items[0].content;
|
||||
const firstStoryContent = mockBanners.mainPageIB.storyFeed[0].banner;
|
||||
|
||||
mockUseStories.mockReturnValue({ isLoading: false, stories: mockStories });
|
||||
mockUseBanners.mockReturnValue({ isDynamicBannersLoading: false, banners: mockBanners });
|
||||
|
||||
render(<StoriesBlock />);
|
||||
|
||||
const openStoryButton = screen.getByTitle(mockStories[0].name);
|
||||
const openStoryButton = screen.getByTitle(mockBanners.mainPageIB.storyFeed[0].miniature.name);
|
||||
|
||||
fireEvent.click(openStoryButton);
|
||||
|
||||
expect(screen.getByText(firstStoryContent.description)).toBeInTheDocument();
|
||||
expect(screen.getByText(firstStoryContent.buttons[0].text)).toBeInTheDocument();
|
||||
expect(screen.getByText(firstStoryContent.buttons[1].text)).toBeInTheDocument();
|
||||
expect(screen.getByText(firstStoryContent.bannerButtons[0].buttonText)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('должна закрыться карусель с историями при клике на кнопку закрытия', () => {
|
||||
const firstStoryContent = mockStories[0].items[0].content;
|
||||
const firstStoryContent = mockBanners.mainPageIB.storyFeed[0].banner;
|
||||
|
||||
mockUseStories.mockReturnValue({ isLoading: false, stories: mockStories });
|
||||
mockUseBanners.mockReturnValue({ isDynamicBannersLoading: false, banners: mockBanners });
|
||||
|
||||
render(<StoriesBlock />);
|
||||
|
||||
const openStoryButton = screen.getByTitle(mockStories[0].name);
|
||||
const openStoryButton = screen.getByTitle(mockBanners.mainPageIB.storyFeed[0].miniature.name);
|
||||
|
||||
fireEvent.click(openStoryButton);
|
||||
|
||||
@@ -130,9 +126,9 @@ describe('StoriesBlock', () => {
|
||||
});
|
||||
|
||||
test('должен произойти переход по ссылке при клике на кнопку в истории', () => {
|
||||
const firstStoryContent = mockStories[0].items[0].content;
|
||||
const firstStoryContent = mockBanners.mainPageIB.storyFeed[0].banner;
|
||||
|
||||
mockUseStories.mockReturnValue({ isLoading: false, stories: mockStories });
|
||||
mockUseBanners.mockReturnValue({ isDynamicBannersLoading: false, banners: mockBanners });
|
||||
|
||||
const originalWindowOpen = window.open;
|
||||
|
||||
@@ -140,11 +136,11 @@ describe('StoriesBlock', () => {
|
||||
|
||||
render(<StoriesBlock />);
|
||||
|
||||
const openStoryButton = screen.getByTitle(mockStories[0].name);
|
||||
const openStoryButton = screen.getByTitle(mockBanners.mainPageIB.storyFeed[0].miniature.name);
|
||||
|
||||
fireEvent.click(openStoryButton);
|
||||
|
||||
const firstActionButton = screen.getByText(firstStoryContent.buttons[0].text);
|
||||
const firstActionButton = screen.getByText(firstStoryContent.bannerButtons[0].buttonText);
|
||||
|
||||
expect(firstActionButton).toBeInTheDocument();
|
||||
|
||||
@@ -156,7 +152,7 @@ describe('StoriesBlock', () => {
|
||||
});
|
||||
|
||||
test('должен соответствовать снепшоту', () => {
|
||||
mockUseStories.mockReturnValue({ isLoading: false, stories: mockStories });
|
||||
mockUseBanners.mockReturnValue({ isDynamicBannersLoading: false, banners: mockBanners });
|
||||
|
||||
const { asFragment } = render(<StoriesBlock />);
|
||||
|
||||
|
||||
+10
-10
@@ -33,20 +33,20 @@ exports[`StoriesBlock должен соответствовать снепшот
|
||||
>
|
||||
<button
|
||||
class="css-1iorir7"
|
||||
title="Счёт для бизнеса"
|
||||
title="АУСН Доступно"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="css-van0yd"
|
||||
class="css-11rm4p0"
|
||||
>
|
||||
<div
|
||||
class="css-117yjny"
|
||||
color=""
|
||||
>
|
||||
<img
|
||||
alt="Счёт для бизнеса"
|
||||
alt="АУСН Доступно"
|
||||
class="css-1qunxpv"
|
||||
src="http://localhost/stories/assets/miniature_1.webp"
|
||||
src="http://localhost:8000/msb-host/stories/assets/miniature_1.webp"
|
||||
/>
|
||||
<span
|
||||
class="css-1scwbqz"
|
||||
@@ -56,7 +56,7 @@ exports[`StoriesBlock должен соответствовать снепшот
|
||||
color="#FFFFFF"
|
||||
font-size="typography.P3.fontSize.S"
|
||||
>
|
||||
Счёт для бизнеса
|
||||
АУСН Доступно
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
@@ -65,20 +65,20 @@ exports[`StoriesBlock должен соответствовать снепшот
|
||||
</button>
|
||||
<button
|
||||
class="css-1iorir7"
|
||||
title="Депозиты для бизнеса"
|
||||
title="Зарплатный проект"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="css-van0yd"
|
||||
class="css-refupn"
|
||||
>
|
||||
<div
|
||||
class="css-117yjny"
|
||||
color=""
|
||||
>
|
||||
<img
|
||||
alt="Депозиты для бизнеса"
|
||||
alt="Зарплатный проект"
|
||||
class="css-1qunxpv"
|
||||
src="http://localhost/stories/assets/miniature_2.webp"
|
||||
src="http://localhost:8000/msb-host/stories/assets/miniature_2.webp"
|
||||
/>
|
||||
<span
|
||||
class="css-1scwbqz"
|
||||
@@ -88,7 +88,7 @@ exports[`StoriesBlock должен соответствовать снепшот
|
||||
color="#FFFFFF"
|
||||
font-size="typography.P3.fontSize.S"
|
||||
>
|
||||
Депозиты для бизнеса
|
||||
Зарплатный проект
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
+6
-4
@@ -28,18 +28,20 @@ describe('useVisitedStories', () => {
|
||||
expect(result.current.viewedStories).not.toContain(STORY_ID_2);
|
||||
expect(window.localStorage.getItem(VIEWED_STORIES)).not.toBe(JSON.stringify([STORY_ID_2]));
|
||||
|
||||
act(() => result.current.handleViewStoryGroup(STORY_ID_2));
|
||||
act(() =>
|
||||
result.current.handleViewStoryGroup(STORY_ID_2, { ymSpaceCode: 'mainPageIB_storyFeed', ymElementName: 'storyFeed_catch_the_wave' })
|
||||
);
|
||||
|
||||
expect(result.current.viewedStories).toContain(STORY_ID_2);
|
||||
expect(window.localStorage.getItem(VIEWED_STORIES)).toBe(JSON.stringify([STORY_ID_2]));
|
||||
});
|
||||
|
||||
test('все сохраненные истории должны быть уникальными', () => {
|
||||
test('все сохранённые истории должны быть уникальными', () => {
|
||||
const { result } = renderHook(() => useVisitedStories());
|
||||
|
||||
act(() => {
|
||||
result.current.handleViewStoryGroup(STORY_ID_3);
|
||||
result.current.handleViewStoryGroup(STORY_ID_3);
|
||||
result.current.handleViewStoryGroup(STORY_ID_3, { ymSpaceCode: 'mainPageIB_storyFeed', ymElementName: 'storyFeed_catch_the_wave' });
|
||||
result.current.handleViewStoryGroup(STORY_ID_3, { ymSpaceCode: 'mainPageIB_storyFeed', ymElementName: 'storyFeed_catch_the_wave' });
|
||||
});
|
||||
|
||||
expect(result.current.viewedStories).toHaveLength(1);
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import type { DynamicBanner, DynamicBannerButton } from '@msb/http';
|
||||
|
||||
// TODO: в последствии нужно переделать компонент под новую структуру
|
||||
/**
|
||||
* Маппер для преобразования формата динамических баннеров в формат stories.
|
||||
*/
|
||||
const mapStoriesToPreviousFormat = (dynamicBanners: DynamicBanner[]) =>
|
||||
dynamicBanners.map(item => {
|
||||
const { name, banner, miniature, toggles } = item;
|
||||
|
||||
const { adv, bannerButtons = [], image, title, description, ymSpaceCode, ymElementName, gradient } = banner;
|
||||
|
||||
const advertising = adv ? `Реклама. erid: ${adv.erid}\n${adv.name} ИНН ${adv.inn}` : undefined;
|
||||
|
||||
const getLinkType = (button: DynamicBannerButton) => {
|
||||
switch (true) {
|
||||
case button.isEco:
|
||||
return 'eco';
|
||||
case button.buttonLink?.includes('ebg'):
|
||||
return 'guarantees';
|
||||
default:
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
id: name,
|
||||
image: miniature?.image,
|
||||
name: miniature?.name,
|
||||
gradient: miniature?.gradient,
|
||||
ymSpaceCode,
|
||||
ymElementName,
|
||||
toggles,
|
||||
items: [
|
||||
{
|
||||
id: `${name}_more`,
|
||||
gradient,
|
||||
image,
|
||||
showTime: 10_000,
|
||||
content: {
|
||||
title,
|
||||
description,
|
||||
advertising,
|
||||
buttons: Array.isArray(bannerButtons)
|
||||
? bannerButtons.map(button => ({
|
||||
link: button.buttonLink ?? '',
|
||||
text: button.buttonText ?? '',
|
||||
linkType: getLinkType(button),
|
||||
ymSpaceCode: button.ymSpaceCode,
|
||||
ymElementName: button.ymSpaceCode,
|
||||
}))
|
||||
: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
export { mapStoriesToPreviousFormat };
|
||||
@@ -1,6 +1,6 @@
|
||||
import { YM_VIEW_ELEMENT_STORIES_LS_KEY } from '@/shared/constants';
|
||||
import { handleReachGoal, YM_GOALS } from '@msb/shared';
|
||||
import { useRef, useState } from 'react';
|
||||
import { handleReachGoal, YM_GOALS } from '@msb/shared';
|
||||
import { YM_VIEW_ELEMENT_STORIES_LS_KEY } from '@/shared/constants';
|
||||
|
||||
const VIEWED_STORIES = 'stories';
|
||||
|
||||
@@ -28,17 +28,18 @@ export const useVisitedStories = () => {
|
||||
|
||||
const [viewedStories, setViewedStories] = useState<string[]>(initialStories.current);
|
||||
|
||||
const handleViewStoryGroup = (storyGroupId: string, ymCode?: string): void => {
|
||||
const handleViewStoryGroup = (storyGroupId: string, ymCodes: { ymSpaceCode?: string; ymElementName?: string }): void => {
|
||||
const ymViewElementStories = getStories(YM_VIEW_ELEMENT_STORIES_LS_KEY);
|
||||
|
||||
if (ymCode && ymViewElementStories.includes(ymCode)) {
|
||||
if (ymCodes.ymElementName && ymViewElementStories.includes(ymCodes.ymElementName)) {
|
||||
return;
|
||||
} else if (ymCode) {
|
||||
const newValue = [...new Set([...ymViewElementStories, ymCode])];
|
||||
} else if (ymCodes.ymElementName && ymCodes.ymSpaceCode) {
|
||||
const newValue = [...new Set([...ymViewElementStories, ymCodes.ymElementName])];
|
||||
|
||||
localStorage.setItem(YM_VIEW_ELEMENT_STORIES_LS_KEY, JSON.stringify(newValue));
|
||||
|
||||
handleReachGoal(YM_GOALS.VIEW_ELEMENT, {
|
||||
[YM_GOALS.VIEW_ELEMENT]: { main_page: { element_name: ymCode } },
|
||||
[YM_GOALS.VIEW_ELEMENT]: { [ymCodes.ymSpaceCode]: { element_name: `${ymCodes.ymElementName}_more` } },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import type { BestRateDealType, FEATURE_TOGGLE_NAMES } from '@msb/http';
|
||||
import type { DOC_TYPES } from '@msb/shared';
|
||||
|
||||
type Link = 'eco' | 'guarantees';
|
||||
|
||||
interface StoryButton {
|
||||
link: string;
|
||||
text: string;
|
||||
linkType?: Link;
|
||||
linkType?: string | undefined;
|
||||
linkDocType?: DOC_TYPES;
|
||||
ymCode?: string;
|
||||
ymSpaceCode?: string;
|
||||
ymElementName?: string;
|
||||
fallbackHref?: {
|
||||
toggle: FEATURE_TOGGLE_NAMES;
|
||||
href: string;
|
||||
linkType?: Link;
|
||||
linkType?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ import {
|
||||
} from '@msb/shared';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { GRADIENT_STORIES, RADIANT_GRADIENT_STORIES } from '../../constants';
|
||||
import { mapStoriesToPreviousFormat } from '../../lib/mapStoriesList';
|
||||
import { sortStoriesByVisit } from '../../lib/sortStoriesByVisit';
|
||||
import { useVisitedStories } from '../../lib/useVisitedStories';
|
||||
import type { StoryButton } from '../../models';
|
||||
import { useStories } from '../../models';
|
||||
import type { IStoryGroupItem } from '../StoriesGroup';
|
||||
import StoriesGroup, { StoryContent } from '../StoriesGroup';
|
||||
|
||||
@@ -23,13 +23,14 @@ const { ActionButton, TextBlock } = StoryContent;
|
||||
|
||||
const StoriesBlock = (): ReactElement => {
|
||||
const history = useHistory();
|
||||
const { stories, isLoading } = useStories();
|
||||
const { initialStories, viewedStories, handleViewStoryGroup } = useVisitedStories();
|
||||
const { handleReachGoal } = useYaMetrika();
|
||||
|
||||
const { features, error } = useFeatureTogglesContext();
|
||||
|
||||
const { organizations } = useAppContext();
|
||||
const { organizations, banners, isDynamicBannersLoading } = useAppContext();
|
||||
|
||||
const storiesList = useMemo(() => mapStoriesToPreviousFormat(banners.mainPageIB.storyFeed ?? []), [banners.mainPageIB.storyFeed]);
|
||||
|
||||
const getFeatureToggle = useCallback(
|
||||
(toggleKey: string) => {
|
||||
@@ -69,25 +70,27 @@ const StoriesBlock = (): ReactElement => {
|
||||
|
||||
const handleActionButtonClick = useCallback(
|
||||
(button: StoryButton) => {
|
||||
if (button.ymCode) {
|
||||
const { ymSpaceCode, ymElementName, fallbackHref, link, linkType, linkDocType } = button;
|
||||
|
||||
if (ymSpaceCode && ymElementName) {
|
||||
handleReachGoal(YM_GOALS.BUTTON_CLICK, {
|
||||
[YM_GOALS.BUTTON_CLICK]: { main_page: { element_name: button.ymCode } },
|
||||
[YM_GOALS.BUTTON_CLICK]: { [ymSpaceCode]: { element_name: [ymElementName] } },
|
||||
});
|
||||
}
|
||||
|
||||
if (!button.fallbackHref?.toggle || !button.fallbackHref.href) {
|
||||
navigateTo(button.link, button.linkType, button.linkDocType);
|
||||
if (!fallbackHref?.toggle || !fallbackHref.href) {
|
||||
navigateTo(link, linkType, linkDocType);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { isEnabled } = getFeatureToggle(button.fallbackHref.toggle);
|
||||
const { isEnabled } = getFeatureToggle(fallbackHref.toggle);
|
||||
|
||||
// если сервис на который хотим перейти закрыт ФТ, то переводим на аналогичный сервис в БОЛ
|
||||
if (isEnabled) {
|
||||
navigateTo(button.link, button.linkType, button.linkDocType);
|
||||
navigateTo(link, linkType, linkDocType);
|
||||
} else {
|
||||
navigateTo(button.fallbackHref.href, button.fallbackHref.linkType);
|
||||
navigateTo(fallbackHref.href, fallbackHref.linkType);
|
||||
}
|
||||
},
|
||||
[getFeatureToggle, handleReachGoal, navigateTo]
|
||||
@@ -95,29 +98,36 @@ const StoriesBlock = (): ReactElement => {
|
||||
|
||||
const storiesGroup: IStoryGroupItem[] = useMemo(
|
||||
() =>
|
||||
stories.reduce((acc: IStoryGroupItem[], story) => {
|
||||
if (story.name) {
|
||||
storiesList.reduce((acc: IStoryGroupItem[], story) => {
|
||||
const isEnabled =
|
||||
story.toggles?.every(toggleKey => {
|
||||
const feature = features.find(f => f.featureCode === toggleKey);
|
||||
|
||||
return feature?.isEnabled;
|
||||
}) ?? false;
|
||||
|
||||
if (story.name && isEnabled) {
|
||||
acc.push({
|
||||
color: '',
|
||||
id: story.id,
|
||||
id: story.id ?? '',
|
||||
mode: 'dark',
|
||||
name: story.name,
|
||||
ymCode: story.ymCode,
|
||||
dealType: story.dealType,
|
||||
ymSpaceCode: story.ymSpaceCode,
|
||||
ymElementName: story.ymElementName,
|
||||
gradient: RADIANT_GRADIENT_STORIES[story.gradient as keyof typeof RADIANT_GRADIENT_STORIES] || RADIANT_GRADIENT_STORIES.sale2,
|
||||
image: { source: `${window.location.origin}${story.image}${queryStringVersion}` },
|
||||
image: { source: `${story.image}${queryStringVersion}` },
|
||||
items: story.items.map(item => ({
|
||||
id: item.id,
|
||||
color: '',
|
||||
gradient: GRADIENT_STORIES[item.gradient as keyof typeof GRADIENT_STORIES] || GRADIENT_STORIES.sale2,
|
||||
image: { source: `${window.location.origin}${item.image}${queryStringVersion}` },
|
||||
showTime: item.showTime,
|
||||
image: { source: `${item.image}${queryStringVersion}` },
|
||||
showTime: 10_000,
|
||||
type: 'image',
|
||||
storyContent: (
|
||||
<StoryContent
|
||||
buttons={item.content.buttons.map(button => (
|
||||
<ActionButton
|
||||
key={button.ymCode}
|
||||
key={button.link}
|
||||
dataAction="link"
|
||||
shape="default"
|
||||
size="L"
|
||||
@@ -137,12 +147,12 @@ const StoriesBlock = (): ReactElement => {
|
||||
|
||||
return acc;
|
||||
}, []),
|
||||
[stories, handleActionButtonClick]
|
||||
[storiesList, features, handleActionButtonClick]
|
||||
);
|
||||
|
||||
return (
|
||||
<StoriesGroup
|
||||
isLoading={isLoading}
|
||||
isLoading={isDynamicBannersLoading}
|
||||
storyGroups={sortStoriesByVisit(storiesGroup, initialStories)}
|
||||
viewedStoriesId={viewedStories}
|
||||
onViewStoryGroup={handleViewStoryGroup}
|
||||
|
||||
+6
-5
@@ -1,9 +1,9 @@
|
||||
import { memo, type ReactElement } from 'react';
|
||||
import { memo } from 'react';
|
||||
import type { ReactElement } from 'react';
|
||||
import { lightTheme, Text } from '@fractal-ui/styling';
|
||||
import { FEATURE_TOGGLE_NAMES } from '@msb/http';
|
||||
import { MEDIA, TrackedElement, useBestRatesMainPage, useFeatureToggles, useMediaQuery } from '@msb/shared';
|
||||
import { useToggle } from '../../lib/hooks/useToggle';
|
||||
|
||||
import { SLIDE_WIDTH_DESKTOP, SLIDE_WIDTH_MOBILE } from '../BlockStories/constants';
|
||||
import { ItemName, Layout, Picture, PictureLayout, Wrapper } from './BlockStoriesItem.styles';
|
||||
import type { IBlockStoriesItemProps } from './types';
|
||||
@@ -14,7 +14,8 @@ const BlockStoriesItem = ({
|
||||
onClick,
|
||||
onViewElement,
|
||||
image,
|
||||
ymCode,
|
||||
ymElementName,
|
||||
ymSpaceCode,
|
||||
color,
|
||||
title,
|
||||
gradient,
|
||||
@@ -55,11 +56,11 @@ const BlockStoriesItem = ({
|
||||
<ItemName>
|
||||
<Text.P3Short as="span" color={lightTheme.colors.control.bg}>
|
||||
{title}{' '}
|
||||
{isDynamicBannersEnabled && bestRates && dealType && bestRates[dealType] && `до ${bestRates[dealType]!.replace('.', ',')}%`}
|
||||
{isDynamicBannersEnabled && bestRates && dealType && bestRates[dealType] && `до ${bestRates[dealType].replace('.', ',')}%`}
|
||||
</Text.P3Short>
|
||||
</ItemName>
|
||||
</PictureLayout>
|
||||
<TrackedElement goalParams={{ main_page: { element_name: ymCode } }} />
|
||||
{ymSpaceCode && ymElementName && <TrackedElement goalParams={{ [ymSpaceCode]: { element_name: [ymElementName] } }} />}
|
||||
</Layout>
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
+2
-1
@@ -15,7 +15,8 @@ export interface IBlockStoriesItemProps {
|
||||
isViewed?: boolean;
|
||||
gradient?: string;
|
||||
dealType?: BestRateDealType;
|
||||
ymCode?: string;
|
||||
ymSpaceCode?: string;
|
||||
ymElementName?: string;
|
||||
onClick?(): void;
|
||||
onViewElement?(): void;
|
||||
}
|
||||
|
||||
+2
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import type React from 'react';
|
||||
import { useRef } from 'react';
|
||||
|
||||
@@ -60,6 +61,7 @@ export function useStoriesNavigation({ onPrevious, onNext, onPause, isActive }:
|
||||
|
||||
const isClick = Math.abs(Number(mouseDownClientX.current) - mouseUpClientX) <= 5;
|
||||
|
||||
// @ts-ignore
|
||||
if (!isClick || (mouseDown.current && pause)) {
|
||||
play();
|
||||
|
||||
|
||||
+13
-9
@@ -5,7 +5,8 @@ import { sortStoriesByVisit } from '../../../../lib/sortStoriesByVisit';
|
||||
import { useEvent } from '../../lib/hooks/useEvent';
|
||||
import { BlockStories } from '../BlockStories';
|
||||
import { BlockStoriesItem } from '../BlockStoriesItem';
|
||||
import { StoriesModal, type TStoryRenderer } from '../StoriesModal';
|
||||
import { StoriesModal } from '../StoriesModal';
|
||||
import type { TStoryRenderer } from '../StoriesModal';
|
||||
import { Wrapper } from './StoriesGroup.styles';
|
||||
import type { IStoryGroupItem } from './components/StoryGroupItem';
|
||||
import { StoryGroupItem } from './components/StoryGroupItem';
|
||||
@@ -27,8 +28,8 @@ const StoriesGroup: FC<IStoriesGroupProps> = ({
|
||||
const [storyGroupIndex, setStoryGroupIndex] = useState<number | undefined>(); // истории в гор.слайдере
|
||||
const [storyItems, setStoryItems] = useState<IStoryGroupItem[]>(sortedItems); // истории в карусели
|
||||
|
||||
const handleStoryGroupView = useEvent((storyId: string, ymCode?: string) => {
|
||||
onViewStoryGroup?.(storyId, ymCode);
|
||||
const handleStoryGroupView = useEvent((storyId: string, ymCodes: { ymSpaceCode?: string; ymElementName?: string }) => {
|
||||
onViewStoryGroup?.(storyId, ymCodes);
|
||||
});
|
||||
|
||||
const changeStoriesHandler = (slideIndex?: number): void => {
|
||||
@@ -47,11 +48,11 @@ const StoriesGroup: FC<IStoriesGroupProps> = ({
|
||||
setStoryGroupIndex(slideIndex);
|
||||
setStoryItems(sortedItems);
|
||||
|
||||
const { id, name, ymCode } = items[slideIndex];
|
||||
const { id, name, ymElementName, ymSpaceCode } = items[slideIndex];
|
||||
|
||||
if (ymCode) {
|
||||
if (ymElementName && ymSpaceCode) {
|
||||
handleReachGoal(YM_GOALS.BUTTON_CLICK, {
|
||||
[YM_GOALS.BUTTON_CLICK]: { main_page: { element_name: ymCode } },
|
||||
[YM_GOALS.BUTTON_CLICK]: { [ymSpaceCode]: { element_name: [ymElementName] } },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -79,7 +80,7 @@ const StoriesGroup: FC<IStoriesGroupProps> = ({
|
||||
};
|
||||
|
||||
const renderGroupItem = (storiesGroup: IStoryGroupItem, index: number): ReactElement => {
|
||||
const { image, name, color, id, gradient, dealType, ymCode } = storiesGroup;
|
||||
const { image, name, color, id, gradient, dealType, ymElementName, ymSpaceCode } = storiesGroup;
|
||||
const isViewedStory = viewedStoriesId.includes(id);
|
||||
const imageSource: string | undefined = typeof image === 'string' ? image : image?.source;
|
||||
|
||||
@@ -92,7 +93,8 @@ const StoriesGroup: FC<IStoriesGroupProps> = ({
|
||||
image={imageSource}
|
||||
isViewed={isViewedStory}
|
||||
title={name}
|
||||
ymCode={ymCode}
|
||||
ymElementName={ymElementName}
|
||||
ymSpaceCode={ymSpaceCode}
|
||||
onClick={() => onShowHandler(index)}
|
||||
onViewElement={() => handleViewGroupItem(storiesGroup, index)}
|
||||
/>
|
||||
@@ -117,7 +119,9 @@ const StoriesGroup: FC<IStoriesGroupProps> = ({
|
||||
onHandleStoryEvent={onHandleStoryEvent}
|
||||
onNextStory={nextStoryHandler}
|
||||
onPreviousStory={onPreviousStory}
|
||||
onViewStoryGroup={() => handleStoryGroupView(storyGroup.id, storyGroup.ymCode ? `${storyGroup.ymCode}_more` : '')}
|
||||
onViewStoryGroup={() =>
|
||||
handleStoryGroupView(storyGroup.id, { ymElementName: storyGroup.ymElementName, ymSpaceCode: storyGroup.ymSpaceCode })
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-2
@@ -30,8 +30,9 @@ export interface IStoryGroupItem {
|
||||
image?: TStoryImage | string;
|
||||
/** Градиент обводки миниатюры */
|
||||
gradient?: string;
|
||||
/** Код Яндекс метрики */
|
||||
ymCode?: string;
|
||||
/** Коды Яндекс метрики */
|
||||
ymSpaceCode?: string;
|
||||
ymElementName?: string;
|
||||
/** Код привязки к динамической ставке */
|
||||
dealType?: BestRateDealType;
|
||||
}
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ export interface IStoriesGroupProps extends IBaseComponent {
|
||||
/** Обработчик событий истории. */
|
||||
onHandleStoryEvent?: StoryEventHandler;
|
||||
/** Событие просмотра группы историй. */
|
||||
onViewStoryGroup?(storyGroupId: string, ymCode?: string): void;
|
||||
onViewStoryGroup?(storyGroupId: string, ymCodes: { ymSpaceCode?: string; ymElementName?: string }): void;
|
||||
/** Список id просмотренных групп историй. */
|
||||
viewedStoriesId?: string[];
|
||||
/** Сортировка историй после просмотра. */
|
||||
|
||||
@@ -2,7 +2,7 @@ export { FinancialDashboard } from './FinancialDashboard';
|
||||
export { Balance } from './Balance';
|
||||
export { QuickActions } from './QuickActions';
|
||||
export { ProductCarousel } from './ProductCarousel';
|
||||
export { default as StoriesBlock } from './StoriesBlock';
|
||||
export { StoriesBlock } from './StoriesBlock';
|
||||
export { OperationsHistory } from './OperationsHistory';
|
||||
export { ClientFeedback } from './ClientFeedback';
|
||||
export { ImportantNotifications } from './ImportantNotifications';
|
||||
|
||||
Reference in New Issue
Block a user