From e227b5ac3029d15d7cc8bbc65046136ddea52439 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Thu, 7 Aug 2025 10:42:46 -0300 Subject: [PATCH] onboarding: Add young account treatment to AI upsell card (#35785) Release Notes: - N/A --- crates/ai_onboarding/src/ai_upsell_card.rs | 213 +++++++++++++-------- crates/onboarding/src/ai_setup_page.rs | 1 + 2 files changed, 139 insertions(+), 75 deletions(-) diff --git a/crates/ai_onboarding/src/ai_upsell_card.rs b/crates/ai_onboarding/src/ai_upsell_card.rs index 4e4833f7700ed22f3c7ce86db38143b45ac8765b..65d3866273e9a3cc76beca465e5b110689b194f9 100644 --- a/crates/ai_onboarding/src/ai_upsell_card.rs +++ b/crates/ai_onboarding/src/ai_upsell_card.rs @@ -1,26 +1,33 @@ use std::{sync::Arc, time::Duration}; -use client::{Client, zed_urls}; +use client::{Client, UserStore, zed_urls}; use cloud_llm_client::Plan; use gpui::{ - Animation, AnimationExt, AnyElement, App, IntoElement, RenderOnce, Transformation, Window, - percentage, + Animation, AnimationExt, AnyElement, App, Entity, IntoElement, RenderOnce, Transformation, + Window, percentage, }; use ui::{Divider, Vector, VectorName, prelude::*}; -use crate::{SignInStatus, plan_definitions::PlanDefinitions}; +use crate::{SignInStatus, YoungAccountBanner, plan_definitions::PlanDefinitions}; #[derive(IntoElement, RegisterComponent)] pub struct AiUpsellCard { pub sign_in_status: SignInStatus, pub sign_in: Arc, + pub account_too_young: bool, pub user_plan: Option, pub tab_index: Option, } impl AiUpsellCard { - pub fn new(client: Arc, user_plan: Option) -> Self { + pub fn new( + client: Arc, + user_store: &Entity, + user_plan: Option, + cx: &mut App, + ) -> Self { let status = *client.status().borrow(); + let store = user_store.read(cx); Self { user_plan, @@ -32,6 +39,7 @@ impl AiUpsellCard { }) .detach_and_log_err(cx); }), + account_too_young: store.account_too_young(), tab_index: None, } } @@ -40,6 +48,7 @@ impl AiUpsellCard { impl RenderOnce for AiUpsellCard { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { let plan_definitions = PlanDefinitions; + let young_account_banner = YoungAccountBanner; let pro_section = v_flex() .flex_grow() @@ -158,36 +167,70 @@ impl RenderOnce for AiUpsellCard { SignInStatus::SignedIn => match self.user_plan { None | Some(Plan::ZedFree) => card .child(Label::new("Try Zed AI").size(LabelSize::Large)) - .child( - div() - .max_w_3_4() - .mb_2() - .child(Label::new(description).color(Color::Muted)), - ) - .child(plans_section) - .child( - footer_container - .child( - Button::new("start_trial", "Start 14-day Free Pro Trial") - .full_width() - .style(ButtonStyle::Tinted(ui::TintColor::Accent)) - .when_some(self.tab_index, |this, tab_index| { - this.tab_index(tab_index) - }) - .on_click(move |_, _window, cx| { - telemetry::event!( - "Start Trial Clicked", - state = "post-sign-in" - ); - cx.open_url(&zed_urls::start_trial_url(cx)) - }), + .map(|this| { + if self.account_too_young { + this.child(young_account_banner).child( + v_flex() + .mt_2() + .gap_1() + .child( + h_flex() + .gap_2() + .child( + Label::new("Pro") + .size(LabelSize::Small) + .color(Color::Accent) + .buffer_font(cx), + ) + .child(Divider::horizontal()), + ) + .child(plan_definitions.pro_plan(true)) + .child( + Button::new("pro", "Get Started") + .full_width() + .style(ButtonStyle::Tinted(ui::TintColor::Accent)) + .on_click(move |_, _window, cx| { + telemetry::event!( + "Upgrade To Pro Clicked", + state = "young-account" + ); + cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx)) + }), + ), ) + } else { + this.child( + div() + .max_w_3_4() + .mb_2() + .child(Label::new(description).color(Color::Muted)), + ) + .child(plans_section) .child( - Label::new("No credit card required") - .size(LabelSize::Small) - .color(Color::Muted), - ), - ), + footer_container + .child( + Button::new("start_trial", "Start 14-day Free Pro Trial") + .full_width() + .style(ButtonStyle::Tinted(ui::TintColor::Accent)) + .when_some(self.tab_index, |this, tab_index| { + this.tab_index(tab_index) + }) + .on_click(move |_, _window, cx| { + telemetry::event!( + "Start Trial Clicked", + state = "post-sign-in" + ); + cx.open_url(&zed_urls::start_trial_url(cx)) + }), + ) + .child( + Label::new("No credit card required") + .size(LabelSize::Small) + .color(Color::Muted), + ), + ) + } + }), Some(Plan::ZedProTrial) => card .child(pro_trial_stamp) .child(Label::new("You're in the Zed Pro Trial").size(LabelSize::Large)) @@ -255,48 +298,68 @@ impl Component for AiUpsellCard { Some( v_flex() .gap_4() - .children(vec![example_group(vec![ - single_example( - "Signed Out State", - AiUpsellCard { - sign_in_status: SignInStatus::SignedOut, - sign_in: Arc::new(|_, _| {}), - user_plan: None, - tab_index: Some(0), - } - .into_any_element(), - ), - single_example( - "Free Plan", - AiUpsellCard { - sign_in_status: SignInStatus::SignedIn, - sign_in: Arc::new(|_, _| {}), - user_plan: Some(Plan::ZedFree), - tab_index: Some(1), - } - .into_any_element(), - ), - single_example( - "Pro Trial", - AiUpsellCard { - sign_in_status: SignInStatus::SignedIn, - sign_in: Arc::new(|_, _| {}), - user_plan: Some(Plan::ZedProTrial), - tab_index: Some(1), - } - .into_any_element(), - ), - single_example( - "Pro Plan", - AiUpsellCard { - sign_in_status: SignInStatus::SignedIn, - sign_in: Arc::new(|_, _| {}), - user_plan: Some(Plan::ZedPro), - tab_index: Some(1), - } - .into_any_element(), - ), - ])]) + .items_center() + .max_w_4_5() + .child(single_example( + "Signed Out State", + AiUpsellCard { + sign_in_status: SignInStatus::SignedOut, + sign_in: Arc::new(|_, _| {}), + account_too_young: false, + user_plan: None, + tab_index: Some(0), + } + .into_any_element(), + )) + .child(example_group_with_title( + "Signed In States", + vec![ + single_example( + "Free Plan", + AiUpsellCard { + sign_in_status: SignInStatus::SignedIn, + sign_in: Arc::new(|_, _| {}), + account_too_young: false, + user_plan: Some(Plan::ZedFree), + tab_index: Some(1), + } + .into_any_element(), + ), + single_example( + "Free Plan but Young Account", + AiUpsellCard { + sign_in_status: SignInStatus::SignedIn, + sign_in: Arc::new(|_, _| {}), + account_too_young: true, + user_plan: Some(Plan::ZedFree), + tab_index: Some(1), + } + .into_any_element(), + ), + single_example( + "Pro Trial", + AiUpsellCard { + sign_in_status: SignInStatus::SignedIn, + sign_in: Arc::new(|_, _| {}), + account_too_young: false, + user_plan: Some(Plan::ZedProTrial), + tab_index: Some(1), + } + .into_any_element(), + ), + single_example( + "Pro Plan", + AiUpsellCard { + sign_in_status: SignInStatus::SignedIn, + sign_in: Arc::new(|_, _| {}), + account_too_young: false, + user_plan: Some(Plan::ZedPro), + tab_index: Some(1), + } + .into_any_element(), + ), + ], + )) .into_any_element(), ) } diff --git a/crates/onboarding/src/ai_setup_page.rs b/crates/onboarding/src/ai_setup_page.rs index 098907870b43152e9981e3bcd769f757af6de1dd..6099745c40937a9399b3c22015ddaf1317a6a081 100644 --- a/crates/onboarding/src/ai_setup_page.rs +++ b/crates/onboarding/src/ai_setup_page.rs @@ -286,6 +286,7 @@ pub(crate) fn render_ai_setup_page( .child(AiUpsellCard { sign_in_status: SignInStatus::SignedIn, sign_in: Arc::new(|_, _| {}), + account_too_young: user_store.read(cx).account_too_young(), user_plan: user_store.read(cx).plan(), tab_index: Some({ tab_index += 1;