Skip to content
Draft
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
Binary file added assets/images/august2026-promo-admins.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/images/august2026-promo-employees.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed assets/images/july26-promo.png
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,17 @@ type ProductMarketingWindowProps = {
/** Resolved illustration asset for illustration-backed variants. Typed optional to match ImageSVG's src. */
illustration: IconAsset | undefined;

/** Whether the CTA must wait for its destination data to settle. */
isCtaDisabled?: boolean;

/** Called when the primary CTA is pressed. */
onCtaPress: () => void;

/** Called when the Dismiss button is pressed. */
onDismiss: () => void;
};

function ProductMarketingWindow({variant, illustration, onCtaPress, onDismiss}: ProductMarketingWindowProps) {
function ProductMarketingWindow({variant, illustration, isCtaDisabled = false, onCtaPress, onDismiss}: ProductMarketingWindowProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {shouldUseNarrowLayout, isExtraSmallScreenHeight, isInLandscapeMode} = useResponsiveLayout();
Expand Down Expand Up @@ -116,6 +119,7 @@ function ProductMarketingWindow({variant, illustration, onCtaPress, onDismiss}:
variant={CONST.BUTTON_VARIANT.SUCCESS}
size={buttonSize}
style={styles.flex1}
isDisabled={isCtaDisabled}
onPress={onCtaPress}
sentryLabel={CONST.SENTRY_LABEL.PRODUCT_MARKETING_WINDOW.CTA}
testID="ProductMarketingWindowCTA"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import {useMemoizedLazyIllustrations} from '@hooks/useLazyAsset';
import useOnyx from '@hooks/useOnyx';
import usePermissions from '@hooks/usePermissions';
import usePolicyConnectionsPrefetch from '@hooks/usePolicyConnectionsPrefetch';

import {setNameValuePair} from '@libs/actions/User';
import Navigation from '@libs/Navigation/Navigation';
Expand Down Expand Up @@ -46,6 +48,7 @@ type ProductMarketingWindowManagerProps = {
*/
function ProductMarketingWindowManager({topmostRouteName}: ProductMarketingWindowManagerProps) {
const {login: currentUserLogin = ''} = useCurrentUserPersonalDetails();
const {isBetaEnabled} = usePermissions();
const [activeAdminPolicies, activeAdminPoliciesMetadata] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {
selector: (policies: OnyxCollection<Policy>) => activeAdminPoliciesSelector(policies, currentUserLogin),
});
Expand All @@ -64,12 +67,27 @@ function ProductMarketingWindowManager({topmostRouteName}: ProductMarketingWindo
// assets when the requested names change after mount (e.g. when the audience flips after policies arrive).
const illustrationNames = announcement ? [announcement.admin.visual, announcement.member?.visual].flatMap((visual) => (visual?.type === 'illustration' ? [visual.name] : [])) : [];
const illustrations = useMemoizedLazyIllustrations(illustrationNames);
const targetAdminPolicyID = activeAdminPolicies?.find((policy) => policy.id === activePolicyID)?.id ?? activeAdminPolicies?.at(0)?.id;
const variant = getProductMarketingAnnouncementVariant(announcement, !!targetAdminPolicyID, lastDismissedMarketingWindow);
const targetAdminPolicy = activeAdminPolicies?.find((policy) => policy.id === activePolicyID) ?? activeAdminPolicies?.at(0);
const variant = getProductMarketingAnnouncementVariant(announcement, !!targetAdminPolicy, lastDismissedMarketingWindow);
const isMemberVariantUnavailable = variant === announcement?.member && !isBetaEnabled(CONST.BETAS.CUSTOM_AGENT);
const isVendorMatchingBetaEnabled = isBetaEnabled(CONST.BETAS.VENDOR_MATCHING);
const shouldPrefetchTargetPolicyConnections = isVendorMatchingBetaEnabled && !!targetAdminPolicy && targetAdminPolicy.id !== activePolicyID;
const {isFetchNeeded, isLoadingFetchedFlag, hasBeenFetched} = usePolicyConnectionsPrefetch(targetAdminPolicy, shouldPrefetchTargetPolicyConnections);
const isAdminCtaPending = shouldPrefetchTargetPolicyConnections && (isLoadingFetchedFlag || (isFetchNeeded && hasBeenFetched === undefined));
const isAdminPolicyConnectionDataAvailable = !shouldPrefetchTargetPolicyConnections || hasBeenFetched === true;
const isCoveredByCenteredModalScreen = !!topmostRouteName && CENTERED_MODAL_SCREEN_NAVIGATORS.has(topmostRouteName);
const isLoading = isLoadingOnyxValue(lastDismissedMarketingWindowMetadata, activeAdminPoliciesMetadata, activePolicyIDMetadata, isLoadingAppMetadata, accountMetadata) || isLoadingApp;

if (!announcement || !variant || isLoading || isProductMarketingWindowCovered || isAnonymousSession || isActingAsDelegate || isCoveredByCenteredModalScreen) {
if (
!announcement ||
!variant ||
isMemberVariantUnavailable ||
isLoading ||
isProductMarketingWindowCovered ||
isAnonymousSession ||
isActingAsDelegate ||
isCoveredByCenteredModalScreen
) {
return null;
}

Expand All @@ -82,15 +100,25 @@ function ProductMarketingWindowManager({topmostRouteName}: ProductMarketingWindo
};

const completeCta = () => {
if (isAdminCtaPending) {
return;
}
// Record the dismissal before navigating so the window doesn't flash again during navigation.
persistDismissal();
Navigation.navigate(variant.getCtaRoute(targetAdminPolicyID));
Navigation.navigate(
variant.getCtaRoute({
adminPolicy: targetAdminPolicy,
isVendorMatchingBetaEnabled,
isAdminPolicyConnectionDataAvailable,
}),
);
};

return (
<ProductMarketingWindow
variant={variant}
illustration={variant.visual.type === 'illustration' ? illustrations[variant.visual.name] : undefined}
isCtaDisabled={isAdminCtaPending}
onCtaPress={completeCta}
onDismiss={dismiss}
/>
Expand Down
9 changes: 7 additions & 2 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10346,8 +10346,13 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
productMarketingWindow: {
roleTypes: {
admin: {
heading: 'Neue Rollentypen für Admins',
body: 'Geben Sie Ihrem Team detailliertere Berechtigungen mit neuen Administratorrollen für Karten, Personen und Zahlungen.',
heading: 'Verbesserte Lieferantenzuordnung',
body: 'Erstelle Lieferanten und benutzerdefinierte Regeln, um sie einfach den wichtigsten Buchhaltungssystemen zuzuordnen.',
cta: 'Ausprobieren',
},
member: {
heading: 'Vorgefertigte Agenten für dich',
body: 'Verwende vorgefertigte oder benutzerdefinierte Agenten, um Ausgaben in deinem Namen automatisch zu kontieren, aufzuteilen und einzureichen.',
cta: 'Ausprobieren',
},
},
Expand Down
9 changes: 7 additions & 2 deletions src/languages/el.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10902,8 +10902,13 @@ ${reportName}`,
productMarketingWindow: {
roleTypes: {
admin: {
heading: 'Νέοι τύποι ρόλων για διαχειριστές',
body: 'Δώστε στην ομάδα σας πιο λεπτομερή δικαιώματα με νέους ρόλους διαχειριστή καρτών, ατόμων και πληρωμών.',
heading: 'Βελτιωμένη αντιστοίχιση προμηθευτών',
body: 'Δημιουργήστε προμηθευτές και προσαρμοσμένους κανόνες για εύκολη αντιστοίχιση με τα κυριότερα λογιστικά συστήματα.',
cta: 'Δοκιμάστε το',
},
member: {
heading: 'Προκατασκευασμένοι πράκτορες για εσάς',
body: 'Χρησιμοποιήστε προκατασκευασμένους ή προσαρμοσμένους πράκτορες για να κωδικοποιούν, να διαχωρίζουν και να υποβάλλουν αυτόματα έξοδα εκ μέρους σας.',
cta: 'Δοκιμάστε το',
},
},
Expand Down
9 changes: 7 additions & 2 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10499,8 +10499,13 @@ const translations = {
productMarketingWindow: {
roleTypes: {
admin: {
heading: 'New role types for admins',
body: 'Give your team more granular permissions with new card, people, and payments admin roles.',
heading: 'Enhanced vendor mapping',
body: 'Create vendors and custom rules for easy mapping to major accounting packages.',
cta: 'Try it out',
},
member: {
heading: 'Pre-built agents for you',
body: 'Use pre-built or custom agents to code, split, and submit expenses automatically on your behalf.',
cta: 'Try it out',
},
},
Expand Down
9 changes: 7 additions & 2 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10543,8 +10543,13 @@ El plan Controlar empieza en 9 $ por miembro activo al mes.`,
productMarketingWindow: {
roleTypes: {
admin: {
heading: 'Nuevos tipos de roles para admins',
body: 'Ofrece a tu equipo permisos más granulares con los nuevos roles de administrador de tarjetas, personas y pagos.',
heading: 'Mapeo de proveedores mejorado',
body: 'Crea proveedores y reglas personalizadas para asignarlos fácilmente a los principales sistemas de contabilidad.',
cta: 'Pruébalo',
},
member: {
heading: 'Agentes prediseñados para ti',
body: 'Usa agentes prediseñados o personalizados para codificar, dividir y enviar gastos automáticamente en tu nombre.',
cta: 'Pruébalo',
},
},
Expand Down
9 changes: 7 additions & 2 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10379,8 +10379,13 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
productMarketingWindow: {
roleTypes: {
admin: {
heading: 'Nouveaux types de rôles pour les administrateurs',
body: 'Offrez à votre équipe des autorisations plus granulaires avec de nouveaux rôles d’administrateur pour les cartes, les personnes et les paiements.',
heading: 'Mise en correspondance améliorée des fournisseurs',
body: 'Créez des fournisseurs et des règles personnalisées pour les associer facilement aux principaux logiciels de comptabilité.',
cta: 'Essayer',
},
member: {
heading: 'Agents préconfigurés pour vous',
body: 'Utilisez des agents préconfigurés ou personnalisés pour coder, fractionner et soumettre automatiquement des dépenses en votre nom.',
cta: 'Essayer',
},
},
Expand Down
9 changes: 7 additions & 2 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10317,8 +10317,13 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
productMarketingWindow: {
roleTypes: {
admin: {
heading: 'Nuovi tipi di ruolo per gli amministratori',
body: 'Offri al tuo team autorizzazioni più granulari con i nuovi ruoli di amministratore per carte, persone e pagamenti.',
heading: 'Mappatura avanzata dei fornitori',
body: 'Crea fornitori e regole personalizzate per associarli facilmente ai principali software di contabilità.',
cta: 'Provalo',
},
member: {
heading: 'Agenti predefiniti per te',
body: 'Usa agenti predefiniti o personalizzati per codificare, suddividere e inviare automaticamente le spese per tuo conto.',
cta: 'Provalo',
},
},
Expand Down
9 changes: 7 additions & 2 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10180,8 +10180,13 @@ ${reportName}`,
productMarketingWindow: {
roleTypes: {
admin: {
heading: '管理者向けの新しいロールタイプ',
body: '新しいカード、メンバー、支払いの各管理者ロールで、チームにより細かな権限を付与できます。',
heading: 'ベンダーマッピングの強化',
body: '主要な会計システムに簡単にマッピングできるよう、ベンダーとカスタムルールを作成します。',
cta: '試してみる',
},
member: {
heading: 'あなたのための構築済みエージェント',
body: '構築済みまたはカスタムのエージェントを使用して、経費のコード付け、分割、提出をあなたに代わって自動化します。',
cta: '試してみる',
},
},
Expand Down
9 changes: 7 additions & 2 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10287,8 +10287,13 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`,
productMarketingWindow: {
roleTypes: {
admin: {
heading: 'Nieuwe roltypen voor beheerders',
body: 'Geef je team meer gedetailleerde toegangsrechten met nieuwe kaart-, personen- en betalingsbeheerrollen.',
heading: 'Verbeterde leverancierskoppeling',
body: 'Maak leveranciers en aangepaste regels aan om ze eenvoudig aan belangrijke boekhoudpakketten te koppelen.',
cta: 'Probeer het uit',
},
member: {
heading: 'Vooraf gebouwde agents voor jou',
body: 'Gebruik vooraf gebouwde of aangepaste agents om uitgaven automatisch namens jou te coderen, splitsen en indienen.',
cta: 'Probeer het uit',
},
},
Expand Down
11 changes: 8 additions & 3 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10256,9 +10256,14 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`,
productMarketingWindow: {
roleTypes: {
admin: {
heading: 'Nowe typy ról dla administratorów',
body: 'Daj swojemu zespołowi bardziej szczegółowe uprawnienia dzięki nowym rolom administratora kart, osób i płatności.',
cta: 'Wypróbuj to',
heading: 'Ulepszone mapowanie dostawców',
body: 'Twórz dostawców i niestandardowe reguły, aby łatwo mapować ich do najpopularniejszych systemów księgowych.',
cta: 'Wypróbuj',
},
member: {
heading: 'Gotowi agenci dla Ciebie',
body: 'Używaj gotowych lub niestandardowych agentów, aby automatycznie kodować, dzielić i przesyłać wydatki w Twoim imieniu.',
cta: 'Wypróbuj',
},
},
},
Expand Down
9 changes: 7 additions & 2 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10280,8 +10280,13 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`,
productMarketingWindow: {
roleTypes: {
admin: {
heading: 'Novos tipos de função para administradores',
body: 'Dê à sua equipe permissões mais granulares com novos papéis de administrador para cartões, pessoas e pagamentos.',
heading: 'Mapeamento aprimorado de fornecedores',
body: 'Crie fornecedores e regras personalizadas para mapeá-los facilmente nos principais sistemas de contabilidade.',
cta: 'Experimente',
},
member: {
heading: 'Agentes prontos para você',
body: 'Use agentes prontos ou personalizados para codificar, dividir e enviar despesas automaticamente em seu nome.',
cta: 'Experimente',
},
},
Expand Down
11 changes: 8 additions & 3 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9932,9 +9932,14 @@ ${reportName}`,
productMarketingWindow: {
roleTypes: {
admin: {
heading: '新的管理员角色类型',
body: '通过新增卡片、成员和付款管理员角色,为你的团队提供更精细的权限控制。',
cta: '试用一下',
heading: '增强的供应商映射',
body: '创建供应商和自定义规则,以便轻松映射到主流会计软件。',
cta: '试用',
},
member: {
heading: '为你预构建的智能代理',
body: '使用预构建或自定义智能代理,代表你自动对费用进行编码、拆分和提交。',
cta: '试用',
},
},
},
Expand Down
39 changes: 33 additions & 6 deletions src/libs/ProductMarketingWindowUtils.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import July26PromoImage from '@assets/images/july26-promo.png';
import August2026PromoAdminsImage from '@assets/images/august2026-promo-admins.png';
import August2026PromoEmployeesImage from '@assets/images/august2026-promo-employees.png';

import type {IllustrationName} from '@components/Icon/IllustrationLoader';

import type {TranslationPaths} from '@src/languages/types';
import ROUTES from '@src/ROUTES';
import type {Route} from '@src/ROUTES';
import type {Policy} from '@src/types/onyx';

import type {ImageSourcePropType} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';

import {hasVendorFeature} from './PolicyUtils';

type ProductMarketingAnnouncementVisual =
| {
type: 'image';
Expand All @@ -19,6 +23,17 @@ type ProductMarketingAnnouncementVisual =
name: IllustrationName;
};

type ProductMarketingCtaContext = {
/** Admin workspace selected for the announcement. Undefined for member variants. */
adminPolicy?: Policy;

/** Whether the vendor-matching beta is enabled for the current account. */
isVendorMatchingBetaEnabled: boolean;

/** Whether connection hydration completed for the selected admin workspace. */
isAdminPolicyConnectionDataAvailable: boolean;
};

/** One audience-specific content variant of a product marketing announcement. All content is authored by marketing per release. */
type ProductMarketingAnnouncementVariant = {
/** Marketing-supplied product screenshot or fallback illustration shown at the top of the window. */
Expand All @@ -33,8 +48,8 @@ type ProductMarketingAnnouncementVariant = {
/** Label of the primary CTA button. */
ctaLabel: TranslationPaths;

/** Builds the route the primary CTA navigates to. Admin announcements receive the target workspace ID. */
getCtaRoute: (adminPolicyID?: string) => Route;
/** Builds the route the primary CTA navigates to using the selected audience and workspace context. */
getCtaRoute: (context: ProductMarketingCtaContext) => Route;
};

/** A single product marketing announcement with audience-targeted content variants. */
Expand All @@ -55,13 +70,25 @@ type ProductMarketingAnnouncement = {
* announcement is dismissed, nothing is shown until a later release replaces it with a new update key.
*/
const ACTIVE_PRODUCT_MARKETING_ANNOUNCEMENT: ProductMarketingAnnouncement | null = {
updateKey: 'productUpdateJuly2026',
updateKey: 'productUpdateAugust2026',
admin: {
visual: {type: 'image', source: July26PromoImage},
visual: {type: 'image', source: August2026PromoAdminsImage},
heading: 'productMarketingWindow.roleTypes.admin.heading',
body: 'productMarketingWindow.roleTypes.admin.body',
ctaLabel: 'productMarketingWindow.roleTypes.admin.cta',
getCtaRoute: (adminPolicyID) => ROUTES.WORKSPACE_MEMBERS.getRoute(adminPolicyID),
getCtaRoute: ({adminPolicy, isVendorMatchingBetaEnabled, isAdminPolicyConnectionDataAvailable}) => {
if (isAdminPolicyConnectionDataAvailable && hasVendorFeature(adminPolicy, isVendorMatchingBetaEnabled)) {
return ROUTES.WORKSPACE_VENDORS.getRoute(adminPolicy?.id);
}
return ROUTES.WORKSPACE_MORE_FEATURES.getRoute(adminPolicy?.id);
},
},
member: {
visual: {type: 'image', source: August2026PromoEmployeesImage},
heading: 'productMarketingWindow.roleTypes.member.heading',
body: 'productMarketingWindow.roleTypes.member.body',
ctaLabel: 'productMarketingWindow.roleTypes.member.cta',
getCtaRoute: () => ROUTES.SETTINGS_AGENTS_NEW.getRoute(),
},
};

Expand Down
Loading
Loading