@@ -0,0 +1,16 @@ +import { useEffect } from 'react' + +import { getModalById, LOW_BALANCE_OFFER } from '#/features/modals' + +import { LowBalanceOfferPlate } from './low-balance-offer-plate' + + +export const LowBalanceOfferOnStart = () => { + const modal = getModalById(LOW_BALANCE_OFFER) + + useEffect(() => { + modal.setState(true) + }, []) + + return +} @@ -0,0 +1,47 @@ +.container { + width: 100%; + max-width: 570px; + padding: 32px 0 0 0; + display: flex; + flex-direction: column; + background-color: #151518; + align-items: flex-start; +} + +.text { + color: #8B8B8B; + font-size: 17px; + line-height: 1.5; + margin: 16px 0 24px; +} + +.text_heading { + font-weight: 800; + font-size: 48px; + line-height: 42px; +} + +.offersWrapper { + display: flex; + flex-direction: row; + justify-content: center; + flex-wrap: wrap; + gap: 20px; + width: 100%; + margin-bottom: 24px; + border-radius: 30px; + box-shadow: + 0 0 20px rgba(100, 180, 255, 0.12), + 0 0 40px rgba(80, 150, 255, 0.08), +} + +.link { + font-weight: 600; + font-style: SemiBold; + font-size: 16px; + align-self: center; + + p { + color:#80808E; + } +} @@ -0,0 +1,67 @@ +import React, { useEffect, useState } from 'react' +import Link from 'next/link' +import { useSession } from 'next-auth/react' + +import { LOW_BALANCE_OFFER, getModalById, PlateTemplate } from '#/features/modals' +import { c } from '#/shared' +import { accountApi } from '#/shared/api/account-endpoints' +import { getDeviceType } from '#/shared/lib/helpers' +import { IOffer } from '#/views/subscription' +import Offer from '#/views/subscription/ui/offer' + +import styles from './low-balance-offer-plate.module.scss' + +export const LowBalanceOfferPlate = () => { + const modal = getModalById(LOW_BALANCE_OFFER) + const { data } = useSession() + const [offers, setOffers] = useState(null) + + useEffect(() => { + if (data?.access) { + accountApi.getPaymentsPlans(data.access).then((res) => { + if (res) setOffers(res) + }) + } + }, [data?.access]) + + const handleClose = () => { + modal.setState(false) + } + + const offerIndex = modal.getStoreProperty('offerIndex') ?? 1 + const offerUid = modal.getStoreProperty('offerUid'); + + const offerToShow = offerUid + ? offers?.find((o) => o.uid === offerUid) + : offerIndex + ? offers?.[offerIndex] + : offers?.[1] + + return ( + + e.stopPropagation()}> + Осталосьмало токенов... + + Подобрали для Вас наиболее подходящий тариф, + взгляните на преимущества! + + + {offers === null ? ( + Загрузка тарифов... + ) : offerToShow ? ( + + ) : null} + + + Нет, я хочу посмотреть все тарифы + + + + ) +} @@ -0,0 +1,54 @@ +import { useEffect, useRef } from 'react' + +import { useAppSelector } from '#/app/store/store' +import { getModalById, LOW_BALANCE_OFFER } from '#/features/modals' +import { LowBalanceOfferPlate } from './low-balance-offer-plate' +import type { ResponseAllInfo } from '#/entities/user-account/model/user-type-slice' +import { useFeatureFlag } from '#/shared/lib/hooks/use-feature-flag' +import { balanceSelector } from '#/shared/lib/selectors' + +export const LowBalanceOfferTrigger = () => { + const balance = useAppSelector(balanceSelector) + const me = useAppSelector((state) => state.user) as ResponseAllInfo & { status: string; referral: string } + const isFewTokensModalEnabled = useFeatureFlag('few-tokens-modal-enabled') + const prevBalanceRef = useRef(null) + const modal = getModalById(LOW_BALANCE_OFFER) + + useEffect(() => { + + if (!isFewTokensModalEnabled) return + + // Пропускаем первый рендер (начальная загрузка баланса) + if (prevBalanceRef.current === null) { + prevBalanceRef.current = balance + return + } + + if (prevBalanceRef.current !== balance) { + prevBalanceRef.current = balance + + if (typeof window !== 'undefined' && sessionStorage.getItem('low-balance-offer-shown') === 'true') { + return + } + + const tokensPerPlan = Number(me.payment_plan?.plan?.tokens_per_plan) + const isFreePlan = tokensPerPlan <= 10 + const threshold = tokensPerPlan * 0.15 + const freePlanThreshold = tokensPerPlan * 0.3 + + if (!isFreePlan && balance < threshold) { + modal.setState(true, { offerUid: me.payment_plan?.plan?.uid, me }) + sessionStorage.setItem('low-balance-offer-shown', 'true') + return + } + + if (isFreePlan && balance < freePlanThreshold) { + modal.setState(true, { offerIndex: 1 }) + sessionStorage.setItem('low-balance-offer-shown', 'true') + return + } + } + }, [balance, me, modal, isFewTokensModalEnabled]) + + return +} @@ -0,0 +1,3 @@ +export { LowBalanceOfferPlate } from './ui/low-balance-offer-plate' +export { LowBalanceOfferOnStart } from './ui/low-balance-offer-on-start' +export { LowBalanceOfferTrigger } from './ui/low-balance-offer-trigger' @@ -2,3 +2,4 @@ export const PLATE_CHANGE_PASSWORD = 'plate-change-password' export const RESEND_INVATION_PASSWORD = 'resend-invation-password' export const ERROR_REPORT = 'error-report' export const BUSINESS_ERROR_REPORT = 'business-error-report' +export const LOW_BALANCE_OFFER = 'low-balance-offer' @@ -3,7 +3,7 @@ width: 100%; max-width: calc(100dvw); height: 100dvh; - background-color: rgba(0, 0, 0, 0.4); + background-color: rgba(0, 0, 0, 0.7); top: 0; left: 0; transition: all 300ms ease-in-out; @@ -66,7 +66,7 @@ &__content { background-color: var(--new-ui-main-color); - border-radius: 20px; + border-radius: 42px; position: relative; overflow: hidden; height: fit-content; @@ -13,6 +13,7 @@ import { FlagProvider } from '@unleash/proxy-client-react' import { store } from '#/app/store/store' import { Error } from '#/shared' +import { LowBalanceOfferTrigger, LowBalanceOfferOnStart } from '#/features/low-balance-offer' import { TourManager, getTourSteps, TourCard, setTourCompleted } from '#/features/nextstep-tour' import { pingFangFont } from '#/shared/lib/constants/font/font' import { getDeviceType } from '#/shared/lib/helpers' @@ -90,6 +91,7 @@ function AppContent({ Component, pageProps }: { Component: NextPageWithLayout; p {Component.getLayout ? Component.getLayout() : } +
Осталосьмало токенов...
+ Подобрали для Вас наиболее подходящий тариф, + взгляните на преимущества! +
Загрузка тарифов...
Нет, я хочу посмотреть все тарифы