ai onboarding: Add first-open upsell card (#35199)

Release Notes:

- N/A
This commit is contained in:
Danilo Leal 2025-07-28 11:00:59 -03:00 committed by GitHub
parent 3a1b1847c1
commit b02ae771cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 204 additions and 0 deletions

View file

@ -1,12 +1,14 @@
mod agent_api_keys_onboarding;
mod agent_panel_onboarding_card;
mod agent_panel_onboarding_content;
mod ai_upsell_card;
mod edit_prediction_onboarding_content;
mod young_account_banner;
pub use agent_api_keys_onboarding::{ApiKeysWithProviders, ApiKeysWithoutProviders};
pub use agent_panel_onboarding_card::AgentPanelOnboardingCard;
pub use agent_panel_onboarding_content::AgentPanelOnboarding;
pub use ai_upsell_card::AiUpsellCard;
pub use edit_prediction_onboarding_content::EditPredictionOnboarding;
pub use young_account_banner::YoungAccountBanner;
@ -54,6 +56,7 @@ impl RenderOnce for BulletItem {
}
}
#[derive(PartialEq)]
pub enum SignInStatus {
SignedIn,
SigningIn,

View file

@ -0,0 +1,201 @@
use std::sync::Arc;
use client::{Client, zed_urls};
use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
use ui::{Divider, List, Vector, VectorName, prelude::*};
use crate::{BulletItem, SignInStatus};
#[derive(IntoElement, RegisterComponent)]
pub struct AiUpsellCard {
pub sign_in_status: SignInStatus,
pub sign_in: Arc<dyn Fn(&mut Window, &mut App)>,
}
impl AiUpsellCard {
pub fn new(client: Arc<Client>) -> Self {
let status = *client.status().borrow();
Self {
sign_in_status: status.into(),
sign_in: Arc::new(move |_window, cx| {
cx.spawn({
let client = client.clone();
async move |cx| {
client.authenticate_and_connect(true, cx).await;
}
})
.detach();
}),
}
}
}
impl RenderOnce for AiUpsellCard {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let pro_section = v_flex()
.w_full()
.gap_1()
.child(
h_flex()
.gap_2()
.child(
Label::new("Pro")
.size(LabelSize::Small)
.color(Color::Accent)
.buffer_font(cx),
)
.child(Divider::horizontal()),
)
.child(
List::new()
.child(BulletItem::new("500 prompts with Claude models"))
.child(BulletItem::new(
"Unlimited edit predictions with Zeta, our open-source model",
)),
);
let free_section = v_flex()
.w_full()
.gap_1()
.child(
h_flex()
.gap_2()
.child(
Label::new("Free")
.size(LabelSize::Small)
.color(Color::Muted)
.buffer_font(cx),
)
.child(Divider::horizontal()),
)
.child(
List::new()
.child(BulletItem::new("50 prompts with the Claude models"))
.child(BulletItem::new("2,000 accepted edit predictions")),
);
let grid_bg = h_flex().absolute().inset_0().w_full().h(px(240.)).child(
Vector::new(VectorName::Grid, rems_from_px(500.), rems_from_px(240.))
.color(Color::Custom(cx.theme().colors().border.opacity(0.05))),
);
let gradient_bg = div()
.absolute()
.inset_0()
.size_full()
.bg(gpui::linear_gradient(
180.,
gpui::linear_color_stop(
cx.theme().colors().elevated_surface_background.opacity(0.8),
0.,
),
gpui::linear_color_stop(
cx.theme().colors().elevated_surface_background.opacity(0.),
0.8,
),
));
const DESCRIPTION: &str = "Zed offers a complete agentic experience, with robust editing and reviewing features to collaborate with AI.";
let footer_buttons = match self.sign_in_status {
SignInStatus::SignedIn => v_flex()
.items_center()
.gap_1()
.child(
Button::new("sign_in", "Start 14-day Free Pro Trial")
.full_width()
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
.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),
)
.into_any_element(),
_ => Button::new("sign_in", "Sign In")
.full_width()
.style(ButtonStyle::Tinted(ui::TintColor::Accent))
.on_click({
let callback = self.sign_in.clone();
move |_, window, cx| {
telemetry::event!("Start Trial Clicked", state = "pre-sign-in");
callback(window, cx)
}
})
.into_any_element(),
};
v_flex()
.relative()
.p_6()
.pt_4()
.border_1()
.border_color(cx.theme().colors().border)
.rounded_lg()
.overflow_hidden()
.child(grid_bg)
.child(gradient_bg)
.child(Headline::new("Try Zed AI"))
.child(Label::new(DESCRIPTION).color(Color::Muted).mb_2())
.child(
h_flex()
.mt_1p5()
.mb_2p5()
.items_start()
.gap_12()
.child(free_section)
.child(pro_section),
)
.child(footer_buttons)
}
}
impl Component for AiUpsellCard {
fn scope() -> ComponentScope {
ComponentScope::Agent
}
fn name() -> &'static str {
"AI Upsell Card"
}
fn sort_name() -> &'static str {
"AI Upsell Card"
}
fn description() -> Option<&'static str> {
Some("A card presenting the Zed AI product during user's first-open onboarding flow.")
}
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
Some(
v_flex()
.p_4()
.gap_4()
.children(vec![example_group(vec![
single_example(
"Signed Out State",
AiUpsellCard {
sign_in_status: SignInStatus::SignedOut,
sign_in: Arc::new(|_, _| {}),
}
.into_any_element(),
),
single_example(
"Signed In State",
AiUpsellCard {
sign_in_status: SignInStatus::SignedIn,
sign_in: Arc::new(|_, _| {}),
}
.into_any_element(),
),
])])
.into_any_element(),
)
}
}