Skip to content

Commit

Permalink
Update pricing (#1163)
Browse files Browse the repository at this point in the history
* Update pricing

* Whitelist

* adress pr comments

---------

Co-authored-by: Alek Petuskey <[email protected]>
  • Loading branch information
Alek99 and Alek Petuskey authored Jan 10, 2025
1 parent ffd20b1 commit 4979d86
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 79 deletions.
82 changes: 59 additions & 23 deletions pcweb/pages/pricing/calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ def seat_rate(self) -> int:
if self.selected_plan == Tiers.PRO.value:
return 20
elif self.selected_plan == Tiers.TEAM.value:
return 29
return 50

@rx.var(cache=True)
def max_seats(self) -> int:
if self.selected_plan == Tiers.PRO.value:
return 5
elif self.selected_plan == Tiers.TEAM.value:
return 15
return 25

@rx.var(cache=True)
def max_cpu(self) -> int:
Expand All @@ -57,12 +57,19 @@ def max_ram(self) -> int:
def change_plan(self, plan: str) -> None:
self.selected_plan = plan
if plan == Tiers.PRO.value:
if self.estimated_cpu_number > 5:
self.estimated_cpu_number = 5
if self.estimated_ram_gb > 10:
self.estimated_ram_gb = 10
if self.estimated_seats > 5:
self.estimated_seats = 5
self.included_cpu = 1
self.included_ram = .5
self.included_seats = 1
# Enforce Pro tier limits
self.estimated_cpu_number = min(self.estimated_cpu_number, 5)
self.estimated_ram_gb = min(self.estimated_ram_gb, 10)
self.estimated_seats = min(self.estimated_seats, 5)
else:
self.included_cpu = 2
self.included_ram = 3
self.included_seats = 5
# Enforce Team tier minimum seats
self.estimated_seats = max(5, self.estimated_seats)


def calculator(text: str, component: rx.Component, total: str) -> rx.Component:
Expand Down Expand Up @@ -144,6 +151,21 @@ def stepper(
def pricing_widget() -> rx.Component:
return rx.box(
rx.box(
# Tier
calculator(
"Tier",
rx.box(
rx.segmented_control.root(
rx.segmented_control.item("Pro", value="Pro"),
#rx.segmented_control.item("Team (coming soon)", value="Team"),
on_change=BillingState.change_plan,
default_value="Pro",
width="100%",
),
class_name="flex flex-row pt-2 !w-[8.5rem] !h-[2.25rem] mb-2",
),
"",
),
# Team seats
calculator(
"Members",
Expand Down Expand Up @@ -201,39 +223,53 @@ def pricing_widget() -> rx.Component:
rx.center(
rx.flex(
rx.badge(
f"Total: ${calculate_total()}/month",
f"Total: ${calculate_total()}- $20 free credits = ",
rx.text.strong(f"${calculate_total()-20}/mo"),

size='3',
),
size='3',
class_name="mt-6",
class_name="mt-6",
)
),
class_name="flex-1 flex flex-col relative h-full w-full max-w-[25rem] pb-2.5 z-[2]",
)


def calculate_total():
return round(
BillingState.estimated_seats * BillingState.seat_rate
+ BillingState.estimated_ram_gb * (BillingState.mem_rate * MONTH_MINUTES)
+ BillingState.estimated_cpu_number * (BillingState.cpu_rate * MONTH_MINUTES)
- 25
# Base price using rx.cond
base_price = rx.cond(
BillingState.selected_plan == Tiers.PRO.value,
20,
250
)

# Calculate additional seats cost
additional_seats = rx.cond(
BillingState.estimated_seats > 1,
BillingState.estimated_seats - 1,
0
)
seat_cost = additional_seats * BillingState.seat_rate

compute_cost = (
(BillingState.estimated_ram_gb) * (BillingState.mem_rate * MONTH_MINUTES) +
(BillingState.estimated_cpu_number) * (BillingState.cpu_rate * MONTH_MINUTES)
)

total = base_price + seat_cost + compute_cost
return round(total)


def header() -> rx.Component:
return rx.box(
rx.el.h3(
"Calculate costs.",
"Cost Estimate",
class_name="text-slate-12 text-3xl font-semibold text-center",
id="calculator-header",
),
rx.el.p(
"Simply usage based pricing.",
class_name="text-slate-9 text-3xl font-semibold text-center",
),
rx.el.p(
"We subtract the hobby tier free CPU and RAM from your usage.",
class_name="text-slate-9 text-md font-medium text-center mt-2",
"Get a price estimate for your organization.",
class_name="text-slate-9 text-2xl font-semibold text-center",
),
class_name="flex items-center mb-5 justify-between text-slate-11 flex-col pt-[5rem] mx-auto w-full",
)
Expand Down
46 changes: 24 additions & 22 deletions pcweb/pages/pricing/plan_cards.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,10 @@ def card(
rx.el.li(
rx.icon(feature[0], class_name="!text-slate-9", size=16),
feature[1],
rx.tooltip(
rx.icon("info", class_name="!text-slate-9", size=12),
content=feature[2],
) if len(feature) == 3 else "",
class_name="text-sm font-medium text-slate-11 flex items-center gap-3",
)
for feature in features
Expand Down Expand Up @@ -222,12 +226,12 @@ def plan_cards() -> rx.Component:
return rx.box(
card(
"Hobby",
"Everything you need to get started with Reflex.",
"Everything you need to get started.",
[
("code", "Open Source Framework"),
("heart-handshake", "Community support"),
("user", "1 team member"),
("app-window", "1 Deployed app"),
("app-window", "1 app included", "Free apps shutdown after 1 hour of inactivity, and are restarted on demand (usually within a few seconds)."),
("clock", "1 day log retention"),
("code", "Open Source Framework"),
("file-code", "Starter Templates"),
],
"Start building for free",
Expand All @@ -236,48 +240,46 @@ def plan_cards() -> rx.Component:
popular_card(
"Pro",
"For professional projects and startups.",
[
("server", "Larger machine sizes"),
("users", "Up to 5 team members"),
("app-window", "Up to 5 Deployed apps"),
[
("heart-handshake", "Community support"),
("users", "1 seat incl. (additional seats at $20/mo)"),
("credit-card", "$20 / month free compute credits"),
("app-window", "Up to 5 apps"),
("server", "Customizable machine sizes"),
("clock", "30 days log retention"),
("globe", "Multi-region"),
("brush", "Custom domains"),
("wand", "AI Tools for Building and Debugging"),
("circle-plus", "Everything in Hobby"),
],
"Start with Pro plan",
price="$20/mo + usage",
price="$20/mo + compute",
),
card(
"Team",
"For teams looking to scale their applications.",
[
("mail", "Email support"),
("users", "Up to 25 team members"),
("app-window", "Unlimited Apps"),
("mail", "Email/Slack support"),
("users", "5 seat incl. (additional seats available)"),
("credit-card", "Monthly free compute credits"),
("app-window", "Unlimited apps"),
("git-branch", "Create multiple projects"),
("signal", "Full Website Analytics"),
("lock-keyhole", "One Click Auth"),
("git-branch", "Dev, Stage & Prod Envs"),
("database", "DB Editor UI and Migration Tool"),
("test-tube", "Built-in Testing Framework"),
("circle-plus", "Everything in Pro"),
],
"Contact sales",
),
card(
"Enterprise",
"Get our priority support and a plan tailored to your needs.",
"Get a plan tailored to your business needs.",
[
("headset", "Priority Engineering Support"),
("user-round-plus", "White Glove Onboarding"),
("users", "Unlimited team members"),
("users", "Customized seat amount"),
("user-round-plus", "Personalized integration help"),
("hard-drive", "On Premise Deployment"),
("signal", "Full Analytics Dashboard"),
("clock", "Unlimited log retention"),
("activity", "Error Monitoring and Observability"),
("git-pull-request", "Influence Reflex Roadmap"),
("shield-check", "Custom SSO"),
("shield-check", "Audit logs, SSO, SOC2 Reports"),
("circle-plus", "Everything in Team"),
],
"Contact sales",
Expand Down
53 changes: 19 additions & 34 deletions pcweb/pages/pricing/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
STYLES = {
"cell": "text-slate-12 font-medium text-sm whitespace-nowrap",
"header_cell": "text-slate-12 font-semibold text-lg",
"header_cell_sub": "text-slate-11 font-semibold text-md",
"feature_cell": "text-slate-9 font-medium text-sm whitespace-nowrap",
"button_base": "!text-sm !font-semibold w-full text-nowrap",
}
Expand All @@ -32,27 +33,16 @@
}
"""

# Data configuration
USERS_SECTION = [
("Per Seat Price", "Free", "$20/mo/user", "Contact Sales", "Contact Sales"),
("User Limit", "1", "5", "25", "Unlimited"),
]

FRAMEWORK_SECTION = [
("Open Source Framework", True, True, True, True),
("Starter Templates", True, True, True, True),
("Enterprise Templates", False, False, True, True),
("Templates", True, True, True, True),
("One Click Auth", False, False, True, True),
("Embed Reflex Apps", False, False, True, True),
("Built-in Testing", False, False, True, True),
]

THEME_SECTION = [("Theming", "Builtin Themes", "Builtin Themes", "Custom Themes", "Custom Themes")]

REFLEX_AI_SECTION = [
("Flexgen Website Builder", "5/day", "20/day", "100/day", "Custom"),
("Full-Stack AI Agent", "5/day", "50/day", "250/day", "Custom"),
("AI Assistant / Debugger", "5/day", "50/day", "250/day", "Custom"),
("Number of Generations", "5/month", "100/month/seat", "250/month/seat", "Custom"),
]

DATABASE_SECTION = [
Expand All @@ -64,13 +54,13 @@
HOSTING_TEXT_SECTION = [
("Compute Limits", "1 CPU, .5GB", "5 CPU, 10GB", "Custom", "Custom"),
("Regions", "Single", "Multiple", "Multiple", "Multiple"),
("Custom Domains", "None", "1", "5", "Unlimited"),
("Build logs", "7 day", "30 days", "90 days", "Custom"),
("Runtime logs", "1 day", "7 days", "30 days", "Custom"),
]

HOSTING_BOOLEAN_SECTION = [
("CLI Deployments", True, True, True, True),
("Custom Domains", False, True, True, True),
("Automatic CI / CD Deploy (Github)", False, False, True, True),
("Secrets", True, True, True, True),
("Secret Manager", False, False, True, True),
Expand All @@ -90,11 +80,11 @@
("Rich Permissions Control", False, False, True, True),
("Connect to Analytics Vendors", False, False, True, True),
("Audit Logs", False, False, False, True),
("Custom SSO", False, False, False, True),
("SSO", False, False, False, True),
]

SUPPORT_TEXT_SECTION = [
("Support", "Community", "Community", "Email Support", "Dedicated Support")
("Support", "Community", "Community", "Email/Slack", "Dedicated Support")
]

SUPPORT_BOOLEAN_SECTION = [
Expand Down Expand Up @@ -154,15 +144,18 @@ def create_table_row(cells: list) -> rx.Component:
row_cells = [create_table_cell(cell) for cell in cells]
return rx.table.row(
*row_cells,
class_name="w-full [&>*:not(:first-child)]:text-center bg-slate-1 z-[2] !h-[56px]",
class_name="w-full [&>*:not(:first-child)]:text-center bg-slate-1 z-[2] !h-[50px]",
)


def create_table_row_header(cells: list, coming_soon: bool = False) -> rx.Component:
def create_table_row_header(name: list, coming_soon: bool = False) -> rx.Component:
return rx.table.row(
*[
rx.table.column_header_cell(cell, rx.badge("coming soon", margin_left="0.5rem"), class_name=STYLES["header_cell"]) if cell and coming_soon else rx.table.column_header_cell(cell, class_name=STYLES["header_cell"])
for cell in cells
rx.table.column_header_cell(name, rx.badge("coming soon", margin_left="0.5rem"), class_name=STYLES["header_cell"]) if coming_soon else rx.table.column_header_cell(name, class_name=STYLES["header_cell"]),
rx.table.column_header_cell("Hobby", class_name=STYLES["header_cell_sub"]),
rx.table.column_header_cell("Pro", class_name=STYLES["header_cell_sub"]),
rx.table.column_header_cell("Team", class_name=STYLES["header_cell_sub"]),
rx.table.column_header_cell("Enterprise", class_name=STYLES["header_cell_sub"])
],
class_name="w-full [&>*:not(:first-child)]:text-center bg-slate-2 border border-slate-3 rounded-2xl z-[6] !h-[3.625rem] relative",
padding_x="5rem !important",
Expand Down Expand Up @@ -223,17 +216,10 @@ def table_body_hosting() -> rx.Component:
return rx.table.root(
rx.el.style(TABLE_STYLE),
rx.table.header(
create_table_row_header(["Price", "Hobby", "Pro", "Team", "Enterprise"]),
create_table_row_header("Hosting"),
glow(),
class_name="relative",
),
create_table_body(
*[create_table_row(row) for row in USERS_SECTION],
),
rx.table.header(
create_table_row_header(["Hosting", "", "", ""]),
class_name="relative",
),
create_table_body(
*[create_table_row(row) for row in HOSTING_TEXT_SECTION],
*[
Expand All @@ -242,7 +228,7 @@ def table_body_hosting() -> rx.Component:
],
),
rx.table.header(
create_table_row_header(["Security", "", "", "", ""]),
create_table_row_header("Security"),
class_name="relative",
),
create_table_body(
Expand All @@ -252,7 +238,7 @@ def table_body_hosting() -> rx.Component:
],
),
rx.table.header(
create_table_row_header(["Support", "", "", "", ""]),
create_table_row_header("Support"),
class_name="relative",
),
create_table_body(
Expand All @@ -270,18 +256,17 @@ def table_body_oss() -> rx.Component:
return rx.table.root(
rx.el.style(TABLE_STYLE),
rx.table.header(
create_table_row_header(["Framework","Hobby", "Pro", "Team", "Enterprise"]),
create_table_row_header("Framework"),
class_name="relative",
),
create_table_body(
*[
create_checkmark_row(feature, checks)
for feature, *checks in FRAMEWORK_SECTION
],
*[create_table_row(row) for row in THEME_SECTION],
),
rx.table.header(
create_table_row_header(["Database", "", "", ""]),
create_table_row_header("Database"),
class_name="relative",
),
create_table_body(
Expand All @@ -291,7 +276,7 @@ def table_body_oss() -> rx.Component:
],
),
rx.table.header(
create_table_row_header(["AI", "", "", ""], coming_soon=True),
create_table_row_header("AI", coming_soon=True),
class_name="relative",
),
create_table_body(
Expand Down

0 comments on commit 4979d86

Please sign in to comment.