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}