In the previous Conditional Feature Notices chapter, we learned how to show or hide components based on simple rules.
However, promotional contentβlike "Refer a Friend" or "You have extra credits"βrequires a smarter approach. We can't just show it once and never again (the user might miss it), but we also can't show it every single time (or the user will hate us).
In this final chapter, we will build the Upsell Impression Management system. This system acts like a "Polite Marketer": it knows when to speak up, when to stay quiet, and when to try again because the situation has changed.
Imagine you open your terminal to run a quick command. CLI: "Hey! Invite a friend to get free credits!" You: Ignore.
Ten minutes later, you run another command. CLI: "Hey! Invite a friend to get free credits!"
If this happens 50 times a day, you will stop using the tool. We need a system that enforces a Max Impression Cap (e.g., "Show this maximum 3 times").
We will use the code from GuestPassesUpsell.tsx as our example. This component encourages users to share "Guest Passes" with friends.
The core of this pattern is a boolean function that decides visibility. It combines backend data with local history.
// From GuestPassesUpsell.tsx
function shouldShowGuestPassesUpsell(): boolean {
// 1. Backend Check: Do we have passes?
const { eligible } = checkCachedPassesEligibility();
if (!eligible) return false;
// 2. Interaction Check: Did user already click the link?
const config = getGlobalConfig();
if (config.hasVisitedPasses) return false;
// ... (limit check comes next)
}
/passes page?" If yes, we assume they know about it and stop bothering them.If the user is eligible and hasn't clicked the link yet, we check the "Nag Meter" (the impression count).
// From GuestPassesUpsell.tsx
// ... inside shouldShowGuestPassesUpsell ...
// 3. The Cap: Have we shown this 3 times?
const seenCount = config.passesUpsellSeenCount ?? 0;
if (seenCount >= 3) {
return false; // Don't show it anymore
}
return true; // Safe to show!
}
passesUpsellSeenCount from our global configuration file. If it is 3 or higher, we return false. The component will simply not render.
When we do decide to show the component, we must record it. This usually happens in a useEffect hook or right after the component mounts.
// From GuestPassesUpsell.tsx
export function incrementGuestPassesSeenCount(): void {
saveGlobalConfig(prev => {
// Take the old count and add 1
const newCount = (prev.passesUpsellSeenCount ?? 0) + 1;
return {
...prev,
passesUpsellSeenCount: newCount
};
});
}
seenCount will be higher.Let's visualize the flow of data when the CLI starts up.
What happens if the user uses all their passes, and then a month later, they earn new passes?
If we blocked the message forever because seenCount is 3, they would never know about the new passes! We need logic to detect a "positive change" and reset the counter.
// From GuestPassesUpsell.tsx
function resetIfPassesRefreshed(): void {
const remaining = getCachedRemainingPasses();
const lastSeen = config.passesLastSeenRemaining ?? 0;
// If we have MORE passes now than last time...
if (remaining > lastSeen) {
saveGlobalConfig(prev => ({
...prev,
passesUpsellSeenCount: 0, // RESET THE COUNT!
passesLastSeenRemaining: remaining
}));
}
}
passesUpsellSeenCount to 0, giving the user a fresh set of 3 impressions.
The same pattern is used for "Overage Credits" (extra budget for API usage). Look at how similar the logic is in OverageCreditUpsell.tsx:
// From OverageCreditUpsell.tsx
export function shouldShowOverageCreditUpsell(): boolean {
// 1. Backend Eligibility
if (!isEligibleForOverageCreditGrant()) return false;
const config = getGlobalConfig();
// 2. Cap Check (Max 3)
if ((config.overageCreditUpsellSeenCount ?? 0) >= 3) {
return false;
}
return true;
}
Congratulations! You have completed the LogoV2 tutorial series.
You have built a sophisticated command-line onboarding experience:
By combining these systems, you ensure that new users get a warm welcome, while power users get a fast, distraction-free toolβand nobody gets annoyed by spammy notifications.
Generated by Code IQ