Initial public release of Warp.

Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
David Stern
2026-04-28 08:43:33 -05:00
commit 0dbd3d567a
4982 changed files with 1431549 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
pub mod shared_objects_creation_denied_body;
pub mod shared_objects_creation_denied_modal;
@@ -0,0 +1,251 @@
use crate::appearance::Appearance;
use crate::drive::DriveObjectType;
use crate::ui_components::blended_colors;
use crate::workspaces::workspace::{BillingMetadata, CustomerType};
use warpui::elements::{
Container, CornerRadius, CrossAxisAlignment, Flex, MainAxisSize, MouseStateHandle,
ParentElement, Radius, Shrinkable, Text,
};
use warpui::fonts::Weight;
use warpui::ui_components::button::ButtonVariant;
use warpui::ui_components::components::{Coords, UiComponent, UiComponentStyles};
use warpui::{
platform::Cursor, AppContext, Element, Entity, SingletonEntity, TypedActionView, View,
ViewContext,
};
const BUTTON_PADDING: f32 = 12.;
const BUTTON_FONT_SIZE: f32 = 14.;
const BUTTON_BORDER_RADIUS: f32 = 4.;
const DEFAULT_DELINQUENT_ADMIN_MODAL_SUBHEADER: &str = "Shared drive objects have been restricted due to a subscription payment issue.\n\nPlease update your payment information to restore access.";
const DEFAULT_DELINQUENT_ADMIN_ENTERPRISE_MODAL_SUBHEADER: &str = "Shared drive objects have been restricted due to a subscription payment issue.\n\nPlease contact support@warp.dev to restore access.";
const DEFAULT_DELINQUENT_MODAL_SUBHEADER: &str = "Shared drive objects have been restricted due to a subscription payment issue.\n\nPlease contact a team admin to restore access.";
const DEFAULT_ADMIN_PROSUMER_MODAL_SUBHEADER: &str = "Warp's Pro plan comes with a limited number of shared drive objects.\n\nFor access to unlimited shared drive objects, upgrade to the Turbo plan.";
const DEFAULT_PROSUMER_MODAL_SUBHEADER: &str = "Warp's Pro plan comes with a limited number of shared drive objects.\n\nFor access to unlimited shared drive objects, contact a team admin to upgrade to the Turbo plan.";
const DEFAULT_ADMIN_MODAL_SUBHEADER: &str = "Warp's free plan comes with a limited number of shared drive objects.\n\nFor access to unlimited shared drive objects, upgrade to a paid plan.";
const DEFAULT_MODAL_SUBHEADER: &str = "Warp's free plan comes with a limited number of shared drive objects.\n\nFor access to unlimited shared drive objects, contact a team admin to upgrade to a paid plan.";
const VIEW_PLANS_TEXT: &str = "Compare plans";
const MANAGE_BILLING_BUTTON_TEXT: &str = "Manage billing";
#[derive(Default)]
struct MouseStateHandles {
button_mouse_state: MouseStateHandle,
}
pub struct SharedObjectsCreationDeniedBody {
object_type: Option<DriveObjectType>,
has_admin_permissions: bool,
is_delinquent_due_to_payment_issue: bool,
customer_type: CustomerType,
button_mouse_states: MouseStateHandles,
}
#[derive(Debug, Clone, Copy)]
pub enum SharedObjectsCreationDeniedBodyAction {
Upgrade,
ManageBilling,
}
pub enum SharedObjectsCreationDeniedBodyEvent {
Upgrade,
ManageBilling,
}
impl SharedObjectsCreationDeniedBody {
pub fn new(object_type: Option<DriveObjectType>) -> Self {
Self {
object_type,
has_admin_permissions: false,
is_delinquent_due_to_payment_issue: false,
customer_type: Default::default(),
button_mouse_states: Default::default(),
}
}
pub fn update_state(
&mut self,
object_type: DriveObjectType,
has_admin_permissions: bool,
is_delinquent_due_to_payment_issue: bool,
customer_type: CustomerType,
ctx: &mut ViewContext<Self>,
) {
self.object_type = Some(object_type);
self.has_admin_permissions = has_admin_permissions;
self.is_delinquent_due_to_payment_issue = is_delinquent_due_to_payment_issue;
self.customer_type = customer_type;
ctx.notify();
}
}
impl Entity for SharedObjectsCreationDeniedBody {
type Event = SharedObjectsCreationDeniedBodyEvent;
}
impl View for SharedObjectsCreationDeniedBody {
fn ui_name() -> &'static str {
"SharedObjectsCreationDeniedBody"
}
fn render(&self, app: &AppContext) -> Box<dyn Element> {
let appearance = Appearance::as_ref(app);
let is_stripe_paid_plan = BillingMetadata::is_stripe_paid_plan(self.customer_type);
let sub_header = match self.object_type {
Some(object_type) => {
match (self.is_delinquent_due_to_payment_issue, self.has_admin_permissions, self.customer_type) {
(true, true, _) => {
if is_stripe_paid_plan {
format!("Shared {object_type}s have been restricted due to a subscription payment issue.\n\nPlease update your payment information to restore access.")
} else {
format!("Shared {object_type}s have been restricted due to a subscription payment issue.\n\nPlease contact support@warp.dev to restore access.")
}
},
(true, false, _) => format!("Shared {object_type}s have been restricted due to a subscription payment issue.\n\nPlease contact a team admin to restore access."),
(false, true, CustomerType::Prosumer) => {
format!("Warp's Pro plan comes with a limited number of shared {object_type}s.\n\nFor access to unlimited shared {object_type}s, upgrade to the Build plan.")
}
(false, false, CustomerType::Prosumer) => {
format!("Warp's Pro plan comes with a limited number of shared {object_type}s.\n\nFor access to unlimited shared {object_type}s, contact a team admin to upgrade to the Build plan.")
}
(false, true, _) => format!("Warp's free plan comes with a limited number of shared {object_type}s.\n\nFor access to unlimited shared {object_type}s, upgrade to a paid plan."),
(false, false, _) => format!("Warp's free plan comes with a limited number of shared {object_type}s.\n\nFor access to unlimited shared {object_type}s, contact a team admin to upgrade to a paid plan."),
}
}
_ => match (
self.is_delinquent_due_to_payment_issue,
self.has_admin_permissions,
self.customer_type,
) {
(true, true, _) => {
if is_stripe_paid_plan {
DEFAULT_DELINQUENT_ADMIN_MODAL_SUBHEADER.into()
} else {
DEFAULT_DELINQUENT_ADMIN_ENTERPRISE_MODAL_SUBHEADER.into()
}
}
(true, false, _) => DEFAULT_DELINQUENT_MODAL_SUBHEADER.into(),
(false, true, CustomerType::Prosumer) => {
DEFAULT_ADMIN_PROSUMER_MODAL_SUBHEADER.into()
}
(false, false, CustomerType::Prosumer) => DEFAULT_PROSUMER_MODAL_SUBHEADER.into(),
(false, true, _) => DEFAULT_ADMIN_MODAL_SUBHEADER.into(),
(false, false, _) => DEFAULT_MODAL_SUBHEADER.into(),
},
};
let mut body = Flex::column()
.with_cross_axis_alignment(CrossAxisAlignment::Stretch)
.with_child(
Container::new(
Text::new(sub_header, appearance.ui_font_family(), 14.)
.with_color(blended_colors::text_sub(
appearance.theme(),
appearance.theme().background(),
))
.finish(),
)
.finish(),
);
// Only render an action button if:
// 1. the team is delinquent + user is an admin + the team is on a stripe paid plan
// OR
// 2. if the team is not delinquent.
// In the case where the team is delinquent and user is NOT an admin, or if the
// team is delinquent but the team is not on a stripe paid plan, we don't render
// any action button.
if self.is_delinquent_due_to_payment_issue
&& self.has_admin_permissions
&& is_stripe_paid_plan
{
body.add_child(
Container::new(
Flex::row()
.with_child(
Shrinkable::new(
0.5,
self.render_button(
appearance,
MANAGE_BILLING_BUTTON_TEXT.into(),
self.button_mouse_states.button_mouse_state.clone(),
SharedObjectsCreationDeniedBodyAction::ManageBilling,
),
)
.finish(),
)
.with_main_axis_size(MainAxisSize::Max)
.finish(),
)
.with_margin_top(24.)
.finish(),
)
} else if !self.is_delinquent_due_to_payment_issue {
body.add_child(
Container::new(
Flex::row()
.with_child(
Shrinkable::new(
0.5,
self.render_button(
appearance,
VIEW_PLANS_TEXT.into(),
self.button_mouse_states.button_mouse_state.clone(),
SharedObjectsCreationDeniedBodyAction::Upgrade,
),
)
.finish(),
)
.with_main_axis_size(MainAxisSize::Max)
.finish(),
)
.with_margin_top(24.)
.finish(),
)
}
body.finish()
}
}
impl SharedObjectsCreationDeniedBody {
fn render_button(
&self,
appearance: &Appearance,
label: String,
mouse_state: MouseStateHandle,
action: SharedObjectsCreationDeniedBodyAction,
) -> Box<dyn Element> {
appearance
.ui_builder()
.button(ButtonVariant::Accent, mouse_state)
.with_centered_text_label(label)
.with_style(UiComponentStyles {
font_size: Some(BUTTON_FONT_SIZE),
font_weight: Some(Weight::Semibold),
border_radius: Some(CornerRadius::with_all(Radius::Pixels(BUTTON_BORDER_RADIUS))),
padding: Some(Coords::uniform(BUTTON_PADDING)),
..Default::default()
})
.build()
.with_cursor(Cursor::PointingHand)
.on_click(move |ctx, _, _| ctx.dispatch_typed_action(action))
.finish()
}
}
impl TypedActionView for SharedObjectsCreationDeniedBody {
type Action = SharedObjectsCreationDeniedBodyAction;
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
match action {
SharedObjectsCreationDeniedBodyAction::Upgrade => {
ctx.emit(SharedObjectsCreationDeniedBodyEvent::Upgrade)
}
SharedObjectsCreationDeniedBodyAction::ManageBilling => {
ctx.emit(SharedObjectsCreationDeniedBodyEvent::ManageBilling)
}
}
}
}
@@ -0,0 +1,222 @@
use crate::drive::cloud_object_styling::warp_drive_icon_color;
use crate::drive::DriveObjectType;
use crate::modal::{Modal, ModalEvent};
use crate::server::ids::ServerId;
use crate::themes::theme::Fill;
use crate::ui_components::icons::Icon;
use crate::workspaces::user_workspaces::UserWorkspaces;
use crate::workspaces::workspace::CustomerType;
use std::default::Default;
use warp_core::ui::appearance::Appearance;
use warpui::fonts::Weight;
use warpui::keymap::FixedBinding;
use warpui::presenter::ChildView;
use warpui::ui_components::components::{Coords, UiComponentStyles};
use warpui::AppContext;
use warpui::SingletonEntity;
use warpui::ViewHandle;
use warpui::{Element, Entity, TypedActionView, View, ViewContext};
use super::shared_objects_creation_denied_body::{
SharedObjectsCreationDeniedBody, SharedObjectsCreationDeniedBodyEvent,
};
const DEFAULT_LIMIT_REACHED_MODAL_HEADER: &str = "Shared object limit reached";
pub struct SharedObjectsCreationDeniedModal {
shared_objects_creation_denied_modal: ViewHandle<Modal<SharedObjectsCreationDeniedBody>>,
team_uid: Option<ServerId>,
}
#[derive(Debug)]
pub enum SharedObjectsCreationDeniedModalAction {
Close,
}
pub enum SharedObjectsCreationDeniedModalEvent {
Close,
TeamSettings,
}
pub fn init(app: &mut AppContext) {
use warpui::keymap::macros::*;
app.register_fixed_bindings([FixedBinding::new(
"escape",
SharedObjectsCreationDeniedModalAction::Close,
id!("SharedObjectsCreationDeniedModal"),
)]);
}
impl SharedObjectsCreationDeniedModal {
pub fn new(object_type: Option<DriveObjectType>, ctx: &mut ViewContext<Self>) -> Self {
let shared_objects_creation_denied_body = ctx.add_typed_action_view(
|_ctx: &mut ViewContext<'_, SharedObjectsCreationDeniedBody>| {
SharedObjectsCreationDeniedBody::new(object_type)
},
);
ctx.subscribe_to_view(
&shared_objects_creation_denied_body,
move |me, _, event, ctx| {
me.handle_shared_objects_creation_denied_body_event(event, ctx);
},
);
let shared_objects_creation_denied_modal = ctx.add_typed_action_view(|ctx| {
Modal::new(
Some(DEFAULT_LIMIT_REACHED_MODAL_HEADER.into()),
shared_objects_creation_denied_body,
ctx,
)
.with_modal_style(UiComponentStyles {
width: Some(355.),
..Default::default()
})
.with_header_style(UiComponentStyles {
font_size: Some(16.),
font_weight: Some(Weight::Bold),
padding: Some(Coords {
top: 24.,
bottom: 16.,
left: 24.,
right: 24.,
}),
..Default::default()
})
.with_body_style(UiComponentStyles {
padding: Some(Coords {
top: 0.,
bottom: 24.,
left: 24.,
right: 24.,
}),
..Default::default()
})
.with_background_opacity(100)
.with_dismiss_on_click()
});
ctx.subscribe_to_view(
&shared_objects_creation_denied_modal,
|me, _, event, ctx| match event {
ModalEvent::Close => me.close(ctx),
},
);
Self {
shared_objects_creation_denied_modal,
team_uid: None,
}
}
pub fn close(&mut self, ctx: &mut ViewContext<Self>) {
ctx.emit(SharedObjectsCreationDeniedModalEvent::Close);
}
pub fn update_modal_state(
&mut self,
team_uid: ServerId,
object_type: DriveObjectType,
has_admin_permissions: bool,
is_delinquent_due_to_payment_issue: bool,
customer_type: CustomerType,
ctx: &mut ViewContext<Self>,
) {
let appearance = Appearance::as_ref(ctx);
self.team_uid = Some(team_uid);
let title: Option<String> = if is_delinquent_due_to_payment_issue {
Some(format!("Shared {object_type}s restricted"))
} else {
Some(format!("Shared {object_type}s limit reached"))
};
let (icon, icon_color) = match object_type {
DriveObjectType::Notebook { is_ai_document } => (
Some(Icon::Notebook),
Some(Fill::Solid(warp_drive_icon_color(
appearance,
DriveObjectType::Notebook { is_ai_document },
))),
),
DriveObjectType::Workflow => (
Some(Icon::Workflow),
Some(Fill::Solid(warp_drive_icon_color(
appearance,
DriveObjectType::Workflow,
))),
),
_ => (None, None),
};
self.shared_objects_creation_denied_modal
.update(ctx, |modal, ctx| {
modal.set_title(title);
modal.set_header_icon(icon);
modal.set_header_icon_color(icon_color);
modal
.body()
.update(ctx, |shared_objects_creation_denied_body, ctx| {
shared_objects_creation_denied_body.update_state(
object_type,
has_admin_permissions,
is_delinquent_due_to_payment_issue,
customer_type,
ctx,
);
});
ctx.notify();
});
}
fn handle_shared_objects_creation_denied_body_event(
&mut self,
event: &SharedObjectsCreationDeniedBodyEvent,
ctx: &mut ViewContext<Self>,
) {
match event {
SharedObjectsCreationDeniedBodyEvent::Upgrade => match self.team_uid {
// If team_uid is set, then open up the upgrade page for the team
// directly.
Some(team_uid) => {
ctx.open_url(UserWorkspaces::upgrade_link_for_team(team_uid).as_str());
}
// Otherwise redirect them to the team settings page.
None => ctx.emit(SharedObjectsCreationDeniedModalEvent::TeamSettings),
},
SharedObjectsCreationDeniedBodyEvent::ManageBilling => match self.team_uid {
// If team_uid is set, then open up the manage billing page for the team
// directly. The actual logic that opens the billing portal url in the
// browser is in the handle_model_event method of TeamsPageView.
Some(team_uid) => {
UserWorkspaces::handle(ctx).update(ctx, move |user_workspaces, ctx| {
user_workspaces.generate_stripe_billing_portal_link(team_uid, ctx);
});
}
// Otherwise redirect them to the team settings page.
None => ctx.emit(SharedObjectsCreationDeniedModalEvent::TeamSettings),
},
}
}
}
impl Entity for SharedObjectsCreationDeniedModal {
type Event = SharedObjectsCreationDeniedModalEvent;
}
impl View for SharedObjectsCreationDeniedModal {
fn ui_name() -> &'static str {
"SharedObjectsCreationDeniedModal"
}
fn render(&self, _app: &AppContext) -> Box<dyn Element> {
ChildView::new(&self.shared_objects_creation_denied_modal).finish()
}
}
impl TypedActionView for SharedObjectsCreationDeniedModal {
type Action = SharedObjectsCreationDeniedModalAction;
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
match action {
SharedObjectsCreationDeniedModalAction::Close => self.close(ctx),
}
}
}