ai_upsell_card.rs

  1use std::sync::Arc;
  2
  3use client::{Client, zed_urls};
  4use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
  5use ui::{Divider, List, Vector, VectorName, prelude::*};
  6
  7use crate::{BulletItem, SignInStatus};
  8
  9#[derive(IntoElement, RegisterComponent)]
 10pub struct AiUpsellCard {
 11    pub sign_in_status: SignInStatus,
 12    pub sign_in: Arc<dyn Fn(&mut Window, &mut App)>,
 13}
 14
 15impl AiUpsellCard {
 16    pub fn new(client: Arc<Client>) -> Self {
 17        let status = *client.status().borrow();
 18
 19        Self {
 20            sign_in_status: status.into(),
 21            sign_in: Arc::new(move |_window, cx| {
 22                cx.spawn({
 23                    let client = client.clone();
 24                    async move |cx| client.sign_in_with_optional_connect(true, cx).await
 25                })
 26                .detach_and_log_err(cx);
 27            }),
 28        }
 29    }
 30}
 31
 32impl RenderOnce for AiUpsellCard {
 33    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
 34        let pro_section = v_flex()
 35            .w_full()
 36            .gap_1()
 37            .child(
 38                h_flex()
 39                    .gap_2()
 40                    .child(
 41                        Label::new("Pro")
 42                            .size(LabelSize::Small)
 43                            .color(Color::Accent)
 44                            .buffer_font(cx),
 45                    )
 46                    .child(Divider::horizontal()),
 47            )
 48            .child(
 49                List::new()
 50                    .child(BulletItem::new("500 prompts with Claude models"))
 51                    .child(BulletItem::new(
 52                        "Unlimited edit predictions with Zeta, our open-source model",
 53                    )),
 54            );
 55
 56        let free_section = v_flex()
 57            .w_full()
 58            .gap_1()
 59            .child(
 60                h_flex()
 61                    .gap_2()
 62                    .child(
 63                        Label::new("Free")
 64                            .size(LabelSize::Small)
 65                            .color(Color::Muted)
 66                            .buffer_font(cx),
 67                    )
 68                    .child(Divider::horizontal()),
 69            )
 70            .child(
 71                List::new()
 72                    .child(BulletItem::new("50 prompts with the Claude models"))
 73                    .child(BulletItem::new("2,000 accepted edit predictions")),
 74            );
 75
 76        let grid_bg = h_flex().absolute().inset_0().w_full().h(px(240.)).child(
 77            Vector::new(VectorName::Grid, rems_from_px(500.), rems_from_px(240.))
 78                .color(Color::Custom(cx.theme().colors().border.opacity(0.05))),
 79        );
 80
 81        let gradient_bg = div()
 82            .absolute()
 83            .inset_0()
 84            .size_full()
 85            .bg(gpui::linear_gradient(
 86                180.,
 87                gpui::linear_color_stop(
 88                    cx.theme().colors().elevated_surface_background.opacity(0.8),
 89                    0.,
 90                ),
 91                gpui::linear_color_stop(
 92                    cx.theme().colors().elevated_surface_background.opacity(0.),
 93                    0.8,
 94                ),
 95            ));
 96
 97        const DESCRIPTION: &str = "Zed offers a complete agentic experience, with robust editing and reviewing features to collaborate with AI.";
 98
 99        let footer_buttons = match self.sign_in_status {
100            SignInStatus::SignedIn => v_flex()
101                .items_center()
102                .gap_1()
103                .child(
104                    Button::new("sign_in", "Start 14-day Free Pro Trial")
105                        .full_width()
106                        .style(ButtonStyle::Tinted(ui::TintColor::Accent))
107                        .on_click(move |_, _window, cx| {
108                            telemetry::event!("Start Trial Clicked", state = "post-sign-in");
109                            cx.open_url(&zed_urls::start_trial_url(cx))
110                        }),
111                )
112                .child(
113                    Label::new("No credit card required")
114                        .size(LabelSize::Small)
115                        .color(Color::Muted),
116                )
117                .into_any_element(),
118            _ => Button::new("sign_in", "Sign In")
119                .full_width()
120                .style(ButtonStyle::Tinted(ui::TintColor::Accent))
121                .on_click({
122                    let callback = self.sign_in.clone();
123                    move |_, window, cx| {
124                        telemetry::event!("Start Trial Clicked", state = "pre-sign-in");
125                        callback(window, cx)
126                    }
127                })
128                .into_any_element(),
129        };
130
131        v_flex()
132            .relative()
133            .p_6()
134            .pt_4()
135            .border_1()
136            .border_color(cx.theme().colors().border)
137            .rounded_lg()
138            .overflow_hidden()
139            .child(grid_bg)
140            .child(gradient_bg)
141            .child(Headline::new("Try Zed AI"))
142            .child(Label::new(DESCRIPTION).color(Color::Muted).mb_2())
143            .child(
144                h_flex()
145                    .mt_1p5()
146                    .mb_2p5()
147                    .items_start()
148                    .gap_12()
149                    .child(free_section)
150                    .child(pro_section),
151            )
152            .child(footer_buttons)
153    }
154}
155
156impl Component for AiUpsellCard {
157    fn scope() -> ComponentScope {
158        ComponentScope::Agent
159    }
160
161    fn name() -> &'static str {
162        "AI Upsell Card"
163    }
164
165    fn sort_name() -> &'static str {
166        "AI Upsell Card"
167    }
168
169    fn description() -> Option<&'static str> {
170        Some("A card presenting the Zed AI product during user's first-open onboarding flow.")
171    }
172
173    fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
174        Some(
175            v_flex()
176                .p_4()
177                .gap_4()
178                .children(vec![example_group(vec![
179                    single_example(
180                        "Signed Out State",
181                        AiUpsellCard {
182                            sign_in_status: SignInStatus::SignedOut,
183                            sign_in: Arc::new(|_, _| {}),
184                        }
185                        .into_any_element(),
186                    ),
187                    single_example(
188                        "Signed In State",
189                        AiUpsellCard {
190                            sign_in_status: SignInStatus::SignedIn,
191                            sign_in: Arc::new(|_, _| {}),
192                        }
193                        .into_any_element(),
194                    ),
195                ])])
196                .into_any_element(),
197        )
198    }
199}