Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Row, Col } from 'antd'
import React, { useMemo } from 'react'

import { useFeatureFlags } from '@open-condo/featureflags/FeatureFlagsContext'
import { useIntl } from '@open-condo/next/intl'
import { Alert, Typography } from '@open-condo/ui'

import { CustomChartView, CHART_CONTAINER_HEIGHT } from '@condo/domains/analytics/components/CustomChartView'
import { UI_HIDE_PAID_FEATURES } from '@condo/domains/common/constants/featureflags'
import { SubscriptionGuardWithTooltip } from '@condo/domains/subscription/components'

import { PaymentTotalDataMapper } from './dataMappers'
Expand All @@ -13,6 +15,8 @@ import type { IPaymentChartCard } from './dataMappers'

const PaymentTotalChart: IPaymentChartCard = ({ data }) => {
const intl = useIntl()
const { useFlag } = useFeatureFlags()
const hidePaidFeatures = useFlag(UI_HIDE_PAID_FEATURES)
const ChartTitle = intl.formatMessage({ id: 'pages.reports.paymentsTotal' })
const SumTitle = intl.formatMessage({ id: 'global.sum' })
const PaymentCountTitle = intl.formatMessage({ id: 'pages.reports.paymentCount' })
Expand All @@ -26,7 +30,7 @@ const PaymentTotalChart: IPaymentChartCard = ({ data }) => {
<Typography.Title level={3}>{ChartTitle}</Typography.Title>
</Col>

{data.length > 0 &&
{!hidePaidFeatures && data.length > 0 &&
<SubscriptionGuardWithTooltip
feature='marketplace'
placement='bottom'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import getConfig from 'next/config'
import { useRouter } from 'next/router'
import React, { useCallback, useMemo } from 'react'

import { useFeatureFlags } from '@open-condo/featureflags/FeatureFlagsContext'
import { useIntl } from '@open-condo/next/intl'
import { Button, Tooltip, Typography } from '@open-condo/ui'

import { UI_HIDE_PAID_FEATURES } from '@condo/domains/common/constants/featureflags'
import { SETTINGS_TAB_SUBSCRIPTION } from '@condo/domains/common/constants/settingsTabs'
import { SubscriptionBlockedContent } from '@condo/domains/subscription/components'
import { useActivateSubscriptions } from '@condo/domains/subscription/hooks/useActivateSubscriptions'
Expand All @@ -21,13 +23,17 @@ type BlockedB2BAppTabProps = {

export const BlockedB2BAppTab: React.FC<BlockedB2BAppTabProps> = ({ appId, shortDescription }) => {
const intl = useIntl()
const { useFlag } = useFeatureFlags()
const hidePaidFeatures = useFlag(UI_HIDE_PAID_FEATURES)
const GuardTitle = intl.formatMessage({ id: 'subscription.accessGuard.title' })
const FeatureGuardTitle = intl.formatMessage({ id: 'subscription.accessGuard.feature.title' })
const GoToPlansMessage = intl.formatMessage({ id: 'subscription.accessGuard.goToPlans' })
const FeaturePayButton = intl.formatMessage({ id: 'subscription.accessGuard.feature.payButton' })
const AwaitingPaymentMessage = intl.formatMessage({ id: 'subscription.planCard.requestPending' })
const AwaitingPaymentTooltipMessage = intl.formatMessage({ id: 'subscription.planCard.requestPending.tooltip' })
const LearnMoreMessage = intl.formatMessage({ id: 'subscription.accessGuard.learnMore' })
const UnavailableTitle = intl.formatMessage({ id: 'subscription.accessGuard.unavailable.title' }, { defaultMessage: 'Access denied' })
const UnavailableDescription = intl.formatMessage({ id: 'subscription.accessGuard.unavailable.description' }, { defaultMessage: 'You do not have access to this service' })

const router = useRouter()

Expand Down Expand Up @@ -85,6 +91,16 @@ export const BlockedB2BAppTab: React.FC<BlockedB2BAppTabProps> = ({ appId, short
</>
), [shortDescription, formattedFeaturePrice, forPlanLabel, FreeWithPlanNode])

if (hidePaidFeatures) {
return (
<SubscriptionBlockedContent
title={UnavailableTitle}
description={UnavailableDescription}
primaryButton={null}
/>
)
}

let primaryButton: React.ReactNode
if (!hasFeaturePlan) {
primaryButton = <Button type='primary' onClick={handleGoToPlans}>{GoToPlansMessage}</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import classnames from 'classnames'
import Router from 'next/router'
import React, { CSSProperties, FunctionComponent, ElementType } from 'react'

import { useFeatureFlags } from '@open-condo/featureflags/FeatureFlagsContext'

import { useAIContext } from '@condo/domains/ai/components/AIContext'
import { useLayoutContext } from '@condo/domains/common/components/LayoutContext'
import { UI_HIDE_LAYOUT_HEADER, UI_HIDE_LAYOUT_SIDE_MENU } from '@condo/domains/common/constants/featureflags'

import styles from './BaseLayout.module.css'
import { SideNav } from './components/SideNav'
Expand Down Expand Up @@ -38,6 +41,9 @@ const BaseLayout: React.FC<React.PropsWithChildren<IBaseLayoutProps>> = (props)

const { isCollapsed } = useLayoutContext()
const { isAIOverlayOpen, aiOverlayWidth } = useAIContext()
const { useFlag } = useFeatureFlags()
const isHeaderHidden = useFlag(UI_HIDE_LAYOUT_HEADER)
const isSideMenuHidden = useFlag(UI_HIDE_LAYOUT_SIDE_MENU)

const subLayoutStyle: CSSProperties = {
...style,
Expand All @@ -46,15 +52,15 @@ const BaseLayout: React.FC<React.PropsWithChildren<IBaseLayoutProps>> = (props)

return (
<Layout className={classnames(styles.layout, className)} style={style}>
<SideNav menuData={menuData} onLogoClick={onLogoClick}/>
<Layout
{!isSideMenuHidden && <SideNav menuData={menuData} onLogoClick={onLogoClick}/>}
<Layout
className={classnames(
styles.subLayout,
isCollapsed ? styles.subLayoutCollapsed : styles.subLayoutExpanded

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove side-menu offset when the side menu is hidden

When UI_HIDE_LAYOUT_SIDE_MENU is enabled on desktop, the SideNav is not rendered, but this class still applies either styles.subLayoutExpanded or styles.subLayoutCollapsed; in BaseLayout.module.css those add a left margin equal to the side-menu width/collapsed width. As a result, embedded pages with the side menu hidden still render shifted to the right with a blank gutter, so the layout is not actually full-width.

Useful? React with 👍 / 👎.

)}
style={subLayoutStyle}
>
<Header headerAction={headerAction} TopMenuItems={TopMenuItems} />
{!isHeaderHidden && <Header headerAction={headerAction} TopMenuItems={TopMenuItems} />}
{children}
<Footer />
</Layout>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,26 @@ export const Footer: React.FC = () => {

const localizedFooterConfig = footerConfig?.[intl?.locale] || null

return isLegalInfoEnabled && localizedFooterConfig && (
return (
<Layout.Footer
className={styles.footer}
>
<Row justify='end'>
{
localizedFooterConfig?.privacyPolicyLink &&
<Col>
<SecondaryLink
size='small'
href={localizedFooterConfig?.privacyPolicyLink}
target='_blank'
>
{privacyPolicyText}
</SecondaryLink>
</Col>
}
</Row>
{isLegalInfoEnabled && localizedFooterConfig && (
<Row justify='end'>
{
localizedFooterConfig?.privacyPolicyLink &&
<Col>
<SecondaryLink
size='small'
href={localizedFooterConfig?.privacyPolicyLink}
target='_blank'
>
{privacyPolicyText}
</SecondaryLink>
</Col>
}
</Row>
)}
</Layout.Footer>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import get from 'lodash/get'
import { useRouter } from 'next/router'
import React, { useCallback, useMemo } from 'react'

import { useFeatureFlags } from '@open-condo/featureflags/FeatureFlagsContext'
import { Menu } from '@open-condo/icons'
import { useMutation } from '@open-condo/next/apollo'
import { useAuth } from '@open-condo/next/auth'
Expand All @@ -15,6 +16,7 @@ import { Space } from '@open-condo/ui'
import { useLayoutContext } from '@condo/domains/common/components/LayoutContext'
import { Logo } from '@condo/domains/common/components/Logo'
import { ResidentActions } from '@condo/domains/common/components/ResidentActions/ResidentActions'
import { UI_HIDE_USER_LINKS } from '@condo/domains/common/constants/featureflags'
import { UserMessagesList } from '@condo/domains/notification/components/UserMessagesList'
import { UserMessagesListContextProvider } from '@condo/domains/notification/contexts/UserMessagesListContext'
import { InlineOrganizationSelect } from '@condo/domains/organization/components/OrganizationSelect'
Expand All @@ -39,6 +41,8 @@ export const Header: React.FC<IHeaderProps> = (props) => {
const client = useApolloClient()
const { toggleCollapsed, isMobileView } = useLayoutContext()
const router = useRouter()
const { useFlag } = useFeatureFlags()
const isUserMenuHidden = useFlag(UI_HIDE_USER_LINKS)

const { isAuthenticated } = useAuth()
const { organization } = useOrganization()
Expand Down Expand Up @@ -89,7 +93,7 @@ export const Header: React.FC<IHeaderProps> = (props) => {
<SBBOLIndicator organization={organization} />
<InlineOrganizationSelect/>
</Space>
<UserMenu/>
{!isUserMenuHidden && <UserMenu/>}
</div>
</div>
<div className='appeals-bar'>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Space, Typography } from '@open-condo/ui'

import { useAIContext } from '@condo/domains/ai/components/AIContext'
import { AIFlowButton } from '@condo/domains/ai/components/AIFlowButton'
import { UI_AI_CHAT_WITH_CONDO } from '@condo/domains/common/constants/featureflags'
import { UI_AI_CHAT_WITH_CONDO, UI_HIDE_USER_LINKS } from '@condo/domains/common/constants/featureflags'
import { analytics } from '@condo/domains/common/utils/analytics'
import { UserMessagesList } from '@condo/domains/notification/components/UserMessagesList'
import { InlineOrganizationSelect } from '@condo/domains/organization/components/OrganizationSelect'
Expand All @@ -32,6 +32,7 @@ export const TopMenuItems: React.FC<ITopMenuItemsProps> = (props) => {
const { useFlag } = useFeatureFlags()
const { isAIOverlayOpen, openAIOverlay } = useAIContext()
const isAIChatEnabled = useFlag(UI_AI_CHAT_WITH_CONDO)
const isUserMenuHidden = useFlag(UI_HIDE_USER_LINKS)

const PaymentHistoryLabel = intl.formatMessage({ id: 'subscription.paymentHistory.title' })
const LinkedCardsLabel = intl.formatMessage({ id: 'subscription.linkedCards.title' })
Expand Down Expand Up @@ -69,7 +70,7 @@ export const TopMenuItems: React.FC<ITopMenuItemsProps> = (props) => {
<SBBOLIndicator organization={organization} />
<InlineOrganizationSelect />
</Space>
<UserMenu extraMenuItems={subscriptionMenuItems} />
{!isUserMenuHidden && <UserMenu extraMenuItems={subscriptionMenuItems} />}
<div style={{ maxHeight: '24px' }}>
<UserMessagesList disabled={!hasSubscription} />
</div>
Expand Down
8 changes: 8 additions & 0 deletions apps/condo/domains/common/constants/featureflags.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ const NEWS_ITEM_FILES_MINIAPPS = 'news-item-files-miniapps'
const NEWS_ITEM_FILE_PUBLISHING_TIMEOUT_BY_NEWS_ITEM = 'news-item-file-publishing-timeout-by-news-item'
const CUSTOM_TOUR_STEPS = 'custom-tour-steps'
const UI_LEGAL_INFO = 'ui-legal-info'
const UI_HIDE_USER_LINKS = 'ui-hide-user-links'
const UI_HIDE_LAYOUT_HEADER = 'ui-hide-layout-header'
const UI_HIDE_LAYOUT_SIDE_MENU = 'ui-hide-layout-sideMenu'
const UI_HIDE_PAID_FEATURES = 'ui-hide-paid-features'

module.exports = {
SMS_AFTER_TICKET_CREATION,
Expand Down Expand Up @@ -112,4 +116,8 @@ module.exports = {
NEWS_ITEM_FILE_PUBLISHING_TIMEOUT_BY_NEWS_ITEM,
CUSTOM_TOUR_STEPS,
UI_LEGAL_INFO,
UI_HIDE_USER_LINKS,
UI_HIDE_LAYOUT_HEADER,
UI_HIDE_LAYOUT_SIDE_MENU,
UI_HIDE_PAID_FEATURES,
}
16 changes: 12 additions & 4 deletions apps/condo/domains/common/hooks/useGlobalHints.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,15 @@ import Link from 'next/link'
import { useRouter } from 'next/router'
import { CSSProperties, useCallback, useEffect, useMemo, useState } from 'react'

import { useFeatureFlags } from '@open-condo/featureflags/FeatureFlagsContext'
import { useAuth } from '@open-condo/next/auth'
import { useIntl } from '@open-condo/next/intl'
import { Banner, Carousel, Typography } from '@open-condo/ui'
import { colors } from '@open-condo/ui/colors'

import { UI_HIDE_USER_LINKS } from '@condo/domains/common/constants/featureflags'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

two spaces are enough



interface HintI18n {
title: string
Expand Down Expand Up @@ -43,6 +47,8 @@ const HINTS_WRAPPER_STYLE: CSSProperties = { marginBottom: 40 }
const HINTS_ROW_GUTTERS: RowProps['gutter'] = [0, 8]

export const useGlobalHints = () => {
const { useFlag } = useFeatureFlags()
const isUserLinksHidden = useFlag(UI_HIDE_USER_LINKS)
const intl = useIntl()
const CanDisableHintsMessage = intl.formatMessage({ id: 'global.globalHints.canDisableHints' })
const InProfileMessage = intl.formatMessage({ id: 'global.globalHints.canDisableHints.inProfile' })
Expand Down Expand Up @@ -134,14 +140,16 @@ export const useGlobalHints = () => {
<Typography.Text type='secondary' size='small'>
{CanDisableHintsMessage}
</Typography.Text>&nbsp;
<Link href='/user'>
<Typography.Link size='small'>{InProfileMessage}</Typography.Link>
</Link>
{!isUserLinksHidden && (
<Link href='/user'>
<Typography.Link size='small'>{InProfileMessage}</Typography.Link>
</Link>
)}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</Typography.Text>
</Col>
</Row>
</div>
), [CanDisableHintsMessage, InProfileMessage, MoreMessage, handleBannerClick, hints])
), [CanDisableHintsMessage, InProfileMessage, isUserLinksHidden, MoreMessage, handleBannerClick, hints])

return {
GlobalHints: renderHints,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { useGetAvailableServiceSubscriptionPlansQuery } from '@app/condo/gql'
import { useRouter } from 'next/router'
import React, { useCallback, useMemo, useState } from 'react'

import { useFeatureFlags } from '@open-condo/featureflags/FeatureFlagsContext'
import { useIntl } from '@open-condo/next/intl'
import { useOrganization } from '@open-condo/next/organization'
import { Button, Space, Tooltip, Typography } from '@open-condo/ui'

import { UI_HIDE_PAID_FEATURES } from '@condo/domains/common/constants/featureflags'
import { SETTINGS_TAB_SUBSCRIPTION } from '@condo/domains/common/constants/settingsTabs'
import { getRequiredFeature } from '@condo/domains/subscription/constants/routeFeatureMapping'
import {
Expand All @@ -28,6 +30,8 @@ export interface NoSubscriptionTooltipProps {

export const NoSubscriptionTooltip: React.FC<NoSubscriptionTooltipProps> = ({ children, placement = 'right', feature: featureProp, path, skipTooltip, b2bAppId, tooltipButtonId }) => {
const intl = useIntl()
const { useFlag } = useFeatureFlags()
const hidePaidFeatures = useFlag(UI_HIDE_PAID_FEATURES)
const router = useRouter()
const { organization } = useOrganization()
const { activatedSubscriptions, registerSubscriptionContext, activateLoading } = useActivateSubscriptions()
Expand Down Expand Up @@ -177,7 +181,7 @@ export const NoSubscriptionTooltip: React.FC<NoSubscriptionTooltipProps> = ({ ch
</Space>
)

if (skipTooltip) {
if (hidePaidFeatures || skipTooltip) {
return children
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import Head from 'next/head'
import { useRouter } from 'next/router'
import React, { useMemo, useCallback } from 'react'

import { useFeatureFlags } from '@open-condo/featureflags/FeatureFlagsContext'
import { useAuth } from '@open-condo/next/auth'
import { useIntl } from '@open-condo/next/intl'
import { useOrganization } from '@open-condo/next/organization'
import { Typography, Button, Tooltip } from '@open-condo/ui'

import { PageHeader, PageWrapper } from '@condo/domains/common/components/containers/BaseLayout'
import { Loader } from '@condo/domains/common/components/Loader'
import { UI_HIDE_PAID_FEATURES } from '@condo/domains/common/constants/featureflags'
import { SETTINGS_TAB_SUBSCRIPTION } from '@condo/domains/common/constants/settingsTabs'

import { SubscriptionBlockedContent } from './SubscriptionBlockedContent'
Expand Down Expand Up @@ -72,6 +74,8 @@ const getPageTitle = (pathname: string, intl: any): string => {
export const SubscriptionAccessGuard: React.FC<SubscriptionAccessGuardProps> = ({ children, skipGuard = false }) => {
const router = useRouter()
const intl = useIntl()
const { useFlag } = useFeatureFlags()
const hidePaidFeatures = useFlag(UI_HIDE_PAID_FEATURES)
const GuardTitle = intl.formatMessage({ id: 'subscription.accessGuard.title' })
const GuardDescription = intl.formatMessage({ id: 'subscription.accessGuard.description' })
const GoToPlansMessage = intl.formatMessage({ id: 'subscription.accessGuard.goToPlans' })
Expand All @@ -80,6 +84,8 @@ export const SubscriptionAccessGuard: React.FC<SubscriptionAccessGuardProps> = (
const FeaturePayButton = intl.formatMessage({ id: 'subscription.accessGuard.feature.payButton' })
const AwaitingPaymentMessage = intl.formatMessage({ id: 'subscription.planCard.requestPending' })
const AwaitingPaymentTooltipMessage = intl.formatMessage({ id: 'subscription.planCard.requestPending.tooltip' })
const UnavailableTitle = intl.formatMessage({ id: 'subscription.accessGuard.unavailable.title' }, { defaultMessage: 'Access denied' })
const UnavailableDescription = intl.formatMessage({ id: 'subscription.accessGuard.unavailable.description' }, { defaultMessage: 'You do not have access to this service' })
const { isFeatureAvailable, isB2BAppEnabled, hasSubscription, loading, hasSubscriptionsFeature } = useOrganizationSubscription()
const { organization, isLoading: orgIsLoading } = useOrganization()
const { isLoading: authIsLoading } = useAuth()
Expand Down Expand Up @@ -200,6 +206,24 @@ export const SubscriptionAccessGuard: React.FC<SubscriptionAccessGuardProps> = (
: null

if (isBlocked) {
if (hidePaidFeatures && !hasSubscription) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Logic flaw in feature-flag condition creates non-functional buttons.

The condition hidePaidFeatures && !hasSubscription is too restrictive. When a user has a subscription but hidePaidFeatures=true and they are blocked on a specific feature (e.g., Lines 167-173: miniapp not enabled or required feature not available), the code falls through to the normal blocked screen (Lines 227-273).

In that screen:

  • Line 239: primaryButton becomes "Buy Feature" → calls openPaymentModal
  • Line 256: PaymentModal is null when hidePaidFeatures=true
  • Line 53 of useSubscriptionPaymentModal: openModal returns early, so modal never opens
  • Alternatively, Line 229: primaryButton is "Go to Plans" → routes to /settings?tab=subscription
  • But settings/index.tsx Line 95 hides that tab when hidePaidFeatures=true

Both buttons would be visible but non-functional, breaking the UX.

🔒 Proposed fix

Remove the && !hasSubscription condition so the unavailable screen is shown whenever hidePaidFeatures is enabled and the route is blocked, regardless of subscription status:

-        if (hidePaidFeatures && !hasSubscription) {
+        if (hidePaidFeatures) {
             return (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (hidePaidFeatures && !hasSubscription) {
if (hidePaidFeatures) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/condo/domains/subscription/components/SubscriptionAccessGuard.tsx` at
line 209, The current guard condition in SubscriptionAccessGuard.tsx using "if
(hidePaidFeatures && !hasSubscription)" is too narrow and causes non-functional
buttons for users who do have a subscription; change the condition to check only
"hidePaidFeatures" (remove the "&& !hasSubscription" part) so the
unavailable/blocked UI is shown whenever hidePaidFeatures is true and the route
is blocked; ensure the primaryButton logic and modal flow still rely on
openPaymentModal/useSubscriptionPaymentModal and that PaymentModal usage is
compatible (so buttons won't attempt to open a modal or navigate to
/settings?tab=subscription when hidePaidFeatures prevents those actions).

return (
<>
<Head>
<title>{pageTitle}</title>
</Head>
<PageWrapper>
<PageHeader title={<Typography.Title>{pageTitle}</Typography.Title>} />
<SubscriptionBlockedContent
title={UnavailableTitle}
description={UnavailableDescription}
primaryButton={null}
/>
</PageWrapper>
</>
)
}

let primaryButton: React.ReactNode
if (!hasFeaturePlan) {
primaryButton = <Button id='subscription-access-guard-go-to-plans-button' type='primary' onClick={handleGoToPlans}>{GoToPlansMessage}</Button>
Expand Down Expand Up @@ -229,8 +253,8 @@ export const SubscriptionAccessGuard: React.FC<SubscriptionAccessGuardProps> = (

return (
<>
{PaymentModal}
<SubscriptionTrialEndedModal />
{!hidePaidFeatures && PaymentModal}
{!hidePaidFeatures && <SubscriptionTrialEndedModal />}
<Head>
<title>{pageTitle}</title>
</Head>
Expand All @@ -251,8 +275,8 @@ export const SubscriptionAccessGuard: React.FC<SubscriptionAccessGuardProps> = (

return (
<>
<SubscriptionWelcomeModal />
<SubscriptionTrialEndedModal />
{!hidePaidFeatures && <SubscriptionWelcomeModal />}
{!hidePaidFeatures && <SubscriptionTrialEndedModal />}
{children}
</>
)
Expand Down
Loading
Loading