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