1use component::{Component, ComponentScope, single_example};
2use gpui::{
3 AnyElement, App, ClickEvent, IntoElement, ParentElement, RenderOnce, SharedString, Styled,
4 Window,
5};
6use theme::ActiveTheme;
7use ui::{
8 Button, ButtonCommon, ButtonStyle, Checkbox, Clickable, Color, Label, LabelCommon,
9 RegisterComponent, ToggleState, h_flex, v_flex,
10};
11
12/// A component that displays an upsell message with a call-to-action button
13///
14/// # Example
15/// ```
16/// let upsell = Upsell::new(
17/// "Upgrade to Zed Pro",
18/// "Get access to advanced AI features and more",
19/// "Upgrade Now",
20/// Box::new(|_, _window, cx| {
21/// cx.open_url("https://zed.dev/pricing");
22/// }),
23/// Box::new(|_, _window, cx| {
24/// // Handle dismiss
25/// }),
26/// Box::new(|checked, window, cx| {
27/// // Handle don't show again
28/// }),
29/// );
30/// ```
31#[derive(IntoElement, RegisterComponent)]
32pub struct Upsell {
33 title: SharedString,
34 message: SharedString,
35 cta_text: SharedString,
36 on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
37 on_dismiss: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
38 on_dont_show_again: Box<dyn Fn(bool, &mut Window, &mut App) + 'static>,
39}
40
41impl Upsell {
42 /// Create a new upsell component
43 pub fn new(
44 title: impl Into<SharedString>,
45 message: impl Into<SharedString>,
46 cta_text: impl Into<SharedString>,
47 on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
48 on_dismiss: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
49 on_dont_show_again: Box<dyn Fn(bool, &mut Window, &mut App) + 'static>,
50 ) -> Self {
51 Self {
52 title: title.into(),
53 message: message.into(),
54 cta_text: cta_text.into(),
55 on_click,
56 on_dismiss,
57 on_dont_show_again,
58 }
59 }
60}
61
62impl RenderOnce for Upsell {
63 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
64 v_flex()
65 .w_full()
66 .p_4()
67 .gap_3()
68 .bg(cx.theme().colors().surface_background)
69 .rounded_md()
70 .border_1()
71 .border_color(cx.theme().colors().border)
72 .child(
73 v_flex()
74 .gap_1()
75 .child(
76 Label::new(self.title)
77 .size(ui::LabelSize::Large)
78 .weight(gpui::FontWeight::BOLD),
79 )
80 .child(Label::new(self.message).color(Color::Muted)),
81 )
82 .child(
83 h_flex()
84 .w_full()
85 .justify_between()
86 .items_center()
87 .child(
88 h_flex()
89 .items_center()
90 .gap_1()
91 .child(
92 Checkbox::new("dont-show-again", ToggleState::Unselected).on_click(
93 move |_, window, cx| {
94 (self.on_dont_show_again)(true, window, cx);
95 },
96 ),
97 )
98 .child(
99 Label::new("Don't show again")
100 .color(Color::Muted)
101 .size(ui::LabelSize::Small),
102 ),
103 )
104 .child(
105 h_flex()
106 .gap_2()
107 .child(
108 Button::new("dismiss-button", "No Thanks")
109 .style(ButtonStyle::Subtle)
110 .on_click(self.on_dismiss),
111 )
112 .child(
113 Button::new("cta-button", self.cta_text)
114 .style(ButtonStyle::Filled)
115 .on_click(self.on_click),
116 ),
117 ),
118 )
119 }
120}
121
122impl Component for Upsell {
123 fn scope() -> ComponentScope {
124 ComponentScope::Agent
125 }
126
127 fn name() -> &'static str {
128 "Upsell"
129 }
130
131 fn description() -> Option<&'static str> {
132 Some("A promotional component that displays a message with a call-to-action.")
133 }
134
135 fn preview(window: &mut Window, cx: &mut App) -> Option<AnyElement> {
136 let examples = vec![
137 single_example(
138 "Default",
139 Upsell::new(
140 "Upgrade to Zed Pro",
141 "Get unlimited access to AI features and more with Zed Pro. Unlock advanced AI capabilities and other premium features.",
142 "Upgrade Now",
143 Box::new(|_, _, _| {}),
144 Box::new(|_, _, _| {}),
145 Box::new(|_, _, _| {}),
146 ).render(window, cx).into_any_element(),
147 ),
148 single_example(
149 "Short Message",
150 Upsell::new(
151 "Try Zed Pro for free",
152 "Start your 7-day trial today.",
153 "Start Trial",
154 Box::new(|_, _, _| {}),
155 Box::new(|_, _, _| {}),
156 Box::new(|_, _, _| {}),
157 ).render(window, cx).into_any_element(),
158 ),
159 ];
160
161 Some(v_flex().gap_4().children(examples).into_any_element())
162 }
163}