From 7fe228c133c0edd2a4a6b5ed282bb213f01d4f96 Mon Sep 17 00:00:00 2001 From: Carlos <36110765+carlosabadia@users.noreply.github.com> Date: Thu, 30 Jan 2025 23:28:33 +0100 Subject: [PATCH] replace pricing calculator with compute table (#1198) * replace pricing calculator with compute table * Pricing + Billing Update * Quickstart * Fix comments * Fix spelling --------- Co-authored-by: Alek Petuskey Co-authored-by: Alek Petuskey --- .../hosting/{project.md => adding-members.md} | 0 docs/hosting/app-management.md | 57 +++ docs/hosting/app.md | 224 ---------- docs/hosting/billing.md | 32 ++ docs/hosting/compute.md | 35 ++ docs/hosting/config_file.md | 32 ++ docs/hosting/deploy-quick-start.md | 2 +- docs/hosting/deploy-with-github-actions.md | 2 +- docs/hosting/logs.md | 45 ++ docs/hosting/machine-types.md | 34 ++ docs/hosting/regions.md | 103 +++++ pcweb/components/docpage/sidebar/sidebar.py | 5 +- .../docpage/sidebar/sidebar_items/learn.py | 30 +- pcweb/components/tabs.py | 136 ++++++ pcweb/pages/pricing/calculator.py | 422 ++++++------------ pcweb/pages/pricing/faq.py | 10 +- pcweb/pages/pricing/plan_cards.py | 39 +- pcweb/pages/pricing/pricing.py | 1 + pcweb/pages/pricing/table.py | 32 +- pcweb/whitelist.py | 4 +- 20 files changed, 690 insertions(+), 555 deletions(-) rename docs/hosting/{project.md => adding-members.md} (100%) create mode 100644 docs/hosting/app-management.md delete mode 100644 docs/hosting/app.md create mode 100644 docs/hosting/billing.md create mode 100644 docs/hosting/compute.md create mode 100644 docs/hosting/config_file.md create mode 100644 docs/hosting/logs.md create mode 100644 docs/hosting/machine-types.md create mode 100644 docs/hosting/regions.md create mode 100644 pcweb/components/tabs.py diff --git a/docs/hosting/project.md b/docs/hosting/adding-members.md similarity index 100% rename from docs/hosting/project.md rename to docs/hosting/adding-members.md diff --git a/docs/hosting/app-management.md b/docs/hosting/app-management.md new file mode 100644 index 0000000000..01670a42f0 --- /dev/null +++ b/docs/hosting/app-management.md @@ -0,0 +1,57 @@ +```python exec +import reflex as rx +from reflex_image_zoom import image_zoom +from pcweb.pages.docs import hosting +from pcweb.pages import docs +from pcweb.styles.styles import get_code_style, cell_style +``` + +# App + +In Reflex Cloud an "app" (or "application" or "website") refers to a web application built using the Reflex framework, which can be deployed and managed within the Cloud platform. + +You can deploy an app using the `reflex deploy` command. + +There are many actions you can take in the Cloud UI to manage your app. Below are some of the most common actions you may want to take. + + +## Stopping an App + +To stop an app follow the arrow in the image below and press on the `Stop app` button. Pausing an app will stop it from running and will not be accessible to users until you resume it. In addition, this will stop you being billed for your app. + +```python eval +image_zoom(rx.image(src="/stopping_app.webp", padding_bottom="20px")) +``` + +```md alert info +# CLI Command to stop an app +`reflex cloud apps stop [OPTIONS] [APP_ID]` +``` + +## Deleting an App + +To delete an app click on the `Settings` tab in the Cloud UI on the app page. + +```python eval +image_zoom(rx.image(src="/environment_variables.webp")) +``` + +Then click on the `Danger` tab as shown below. + +```python eval +image_zoom(rx.image(src="/deleting_app.webp")) +``` + +Here there is a `Delete app` button. Pressing this button will delete the app and all of its data. This action is irreversible. + +```md alert info +# CLI Command to delete an app +`reflex cloud apps delete [OPTIONS] [APP_ID]` +``` + + +## Other app settings + +Clicking on the `Settings` tab in the Cloud UI on the app page also allows a user to change the `app name`, change the `app description` and check the `app id`. + +The other app settings also allows users to edit and add secrets (environment variables) to the app. For more information on secrets, see the [Secrets (Environment Variables)]({hosting.secrets_environment_vars.path}) page. diff --git a/docs/hosting/app.md b/docs/hosting/app.md deleted file mode 100644 index 2bfabfacd1..0000000000 --- a/docs/hosting/app.md +++ /dev/null @@ -1,224 +0,0 @@ -```python exec -import reflex as rx -from reflex_image_zoom import image_zoom -from pcweb.pages.docs import hosting -from pcweb.pages import docs -from pcweb.styles.styles import get_code_style, cell_style - - -regions = { - "ams": "Amsterdam, Netherlands", - "arn": "Stockholm, Sweden", - "atl": "Atlanta, Georgia (US)", - "bog": "Bogotá, Colombia", - "bom": "Mumbai, India", - "bos": "Boston, Massachusetts (US)", - "cdg": "Paris, France", - "den": "Denver, Colorado (US)", - "dfw": "Dallas, Texas (US)", - "ewr": "Secaucus, NJ (US)", - "eze": "Ezeiza, Argentina", - "fra": "Frankfurt, Germany", - "gdl": "Guadalajara, Mexico", - "gig": "Rio de Janeiro, Brazil", - "gru": "Sao Paulo, Brazil", - "hkg": "Hong Kong, Hong Kong", - "iad": "Ashburn, Virginia (US)", - "jnb": "Johannesburg, South Africa", - "lax": "Los Angeles, California (US)", - "lhr": "London, United Kingdom", - "mad": "Madrid, Spain", - "mia": "Miami, Florida (US)", - "nrt": "Tokyo, Japan", - "ord": "Chicago, Illinois (US)", - "otp": "Bucharest, Romania", - "phx": "Phoenix, Arizona (US)", - "qro": "Querétaro, Mexico", - "scl": "Santiago, Chile", - "sea": "Seattle, Washington (US)", - "sin": "Singapore, Singapore", - "sjc": "San Jose, California (US)", - "syd": "Sydney, Australia", - "waw": "Warsaw, Poland", - "yul": "Montreal, Canada", - "yyz": "Toronto, Canada" -} - - -``` - -# App - -In Reflex Cloud an "app" (or "application" or "website") refers to a web application built using the Reflex framework, which can be deployed and managed within the Cloud platform. - -You can deploy an app using the `reflex deploy` command. - -There are many actions you can take in the Cloud UI to manage your app. Below are some of the most common actions you may want to take. - - -## VMTypes - - -To scale your app you can choose different VMTypes. VMTypes are different configurations of CPU and RAM. - -To scale your VM in the Cloud UI, click on the `Settings` tab in the Cloud UI on the app page, and then click on the `Scale` tab as shown below. Clicking on the `Change VM` button will allow you to scale your app. - - -```python eval -image_zoom(rx.image(src="/scaling_vms.webp", padding_bottom="20px")) -``` - -### VMTypes in the CLI - -To get all the possible VMTypes you can run the following command: - -```bash -reflex apps vmtypes -``` - -To set which VMType to use when deploying your app you can pass the `--vmtype` flag with the id of the VMType. For example: - -```bash -reflex deploy --project f88b1574-f101-####-####-5f########## --vmtype c2m4 -``` - -This will deploy your app with the `c2m4` VMType, giving your app 2 CPU cores and 4 GB of RAM. - - - -## Regions - -To scale your app you can choose different regions. Regions are different locations around the world where your app can be deployed. - -To scale your app to multiple regions in the Cloud UI, click on the `Settings` tab in the Cloud UI on the app page, and then click on the `Regions` tab as shown below. Clicking on the `Add new region` button will allow you to scale your app to multiple regions. - -```python eval -image_zoom(rx.image(src="/scaling_regions.webp", padding_bottom="20px")) -``` - -The images below show all the regions that can be deployed in. - -```python eval -rx.hstack( - image_zoom(rx.image(src="/regions_1.webp", padding_bottom="20px")), - image_zoom(rx.image(src="/regions_2.webp", padding_bottom="20px")), -) -``` - - -### Selecting Regions to Deploy in the CLI - -Below is an example of how to deploy your app in several regions: - -```bash -reflex deploy --project f88b1574-f101-####-####-5f########## --region sjc --region iad -``` - -By default all apps are deloyed in `sjc` if no other regions are given. If you wish to deploy in another region or several regions you can pass the `--region` flag (`-r` also works) with the region code. Check out all the regions that we can deploy to below: - - -## Config File - -To create a `config.yml` file for your app run the command below: - -```bash -reflex cloud config -``` - -This will create a yaml file similar to the one below where you can edit the app configuration: - -```yaml -name: medo -description: '' -regions: - sjc: 1 - lhr: 2 -vmtype: c1m1 -hostname: null -envfile: .env -project: null -packages: -- procps -``` - - - -## View Logs - -To view the app logs follow the arrow in the image below and press on the `Logs` dropdown. - -```python eval -image_zoom(rx.image(src="/view_logs.webp", padding_bottom="20px")) -``` - -```md alert info -# CLI Command to view logs -`reflex cloud apps logs [OPTIONS] [APP_ID]` -``` - -## View Deployment Logs and Deployment History - -To view the deployment history follow the arrow in the image below and press on the `Deployments`. - -```python eval -image_zoom(rx.image(src="/view_deployment_logs.webp")) -``` - -This brings you to the page below where you can see the deployment history of your app. Click on deployment you wish to explore further. - -```python eval -image_zoom(rx.image(src="/view_deployment_logs_2.webp", padding_bottom="20px")) -``` - -```md alert info -# CLI Command to view deployment history -`reflex cloud apps history [OPTIONS] [APP_ID]` -``` - -This brings you to the page below where you can view the deployment logs of your app by clicking the `Build logs` dropdown. - -```python eval -image_zoom(rx.image(src="/view_deployment_logs_3.webp")) -``` - - -## Stopping an App - -To stop an app follow the arrow in the image below and press on the `Stop app` button. Pausing an app will stop it from running and will not be accessible to users until you resume it. In addition, this will stop you being billed for your app. - -```python eval -image_zoom(rx.image(src="/stopping_app.webp", padding_bottom="20px")) -``` - -```md alert info -# CLI Command to stop an app -`reflex cloud apps stop [OPTIONS] [APP_ID]` -``` - -## Deleting an App - -To delete an app click on the `Settings` tab in the Cloud UI on the app page. - -```python eval -image_zoom(rx.image(src="/environment_variables.webp")) -``` - -Then click on the `Danger` tab as shown below. - -```python eval -image_zoom(rx.image(src="/deleting_app.webp")) -``` - -Here there is a `Delete app` button. Pressing this button will delete the app and all of its data. This action is irreversible. - -```md alert info -# CLI Command to delete an app -`reflex cloud apps delete [OPTIONS] [APP_ID]` -``` - - -## Other app settings - -Clicking on the `Settings` tab in the Cloud UI on the app page also allows a user to change the `app name`, change the `app description` and check the `app id`. - -The other app settings also allows users to edit and add secrets (environment variables) to the app. For more information on secrets, see the [Secrets (Environment Variables)]({hosting.secrets_environment_vars.path}) page. diff --git a/docs/hosting/billing.md b/docs/hosting/billing.md new file mode 100644 index 0000000000..2cbb6d9e77 --- /dev/null +++ b/docs/hosting/billing.md @@ -0,0 +1,32 @@ +```python exec +import reflex as rx +from reflex_image_zoom import image_zoom +from pcweb.pages.pricing.calculator import compute_table_base +from pcweb.pages.docs import hosting +``` + +## Overview + +Billing for Reflex Cloud is monthly per project. Project owners and admins are able to view and manage the billing page. + +The billing for a project is comprised of two parts - number of `seats` and `compute`. + +## Seats + +Projects on a paid plan can invite collaborators to join their project. + +Each additional collaborator is considered a `seat` and is charged on a flat monthly rate. Project owners and admins can manage permissions and roles for each seat in the settings tab on the project page. + +## Compute + +Reflex Cloud is billed on a per second basis so you only pay for when your app is being used by your end users. When your app is idle, you are not charged. + +For more information on compute pricing, please see the [compute]({hosting.compute.path}) page. + +## Manage Billing + +To manage your billing, you can go to the `Billing` tab in the Cloud UI on the project page. + +## Setting Billing Limits + +If you want to set a billing limit for your project, you can do so by going to the `Billing` tab in the Cloud UI on the project page. diff --git a/docs/hosting/compute.md b/docs/hosting/compute.md new file mode 100644 index 0000000000..40d7f8dad2 --- /dev/null +++ b/docs/hosting/compute.md @@ -0,0 +1,35 @@ +```python exec +import reflex as rx +from reflex_image_zoom import image_zoom +from pcweb.pages.pricing.calculator import compute_table_base +``` + +## Compute Usage + +Reflex Cloud is billed on a per second basis so you only pay for when your app is being used by your end users. When your app is idle, you are not charged. + +This allows you to deploy your app on larger sizes and multiple regions without worrying about paying for idle compute. We bill on a per second basis so you only pay for the compute you use. + +By default your app stays alive for 5 minutes after the no users are connected. After this time your app will be considered idle and you will not be charged. Start up times usually take less than 1 second for you apps to come back online. + +#### Warm vs Cold Start +- Apps below `c2m2` are considered warm starts and are usually less than 1 second. +- If your app is larger than `c2m2` it will be a cold start which takes around 15 seconds. If you want to avoid this you can reserve a machine. + +## Compute Pricing Table + +```python eval +compute_table_base() +``` + +## Reserved Machines (Coming Soon) + +If you expect your apps to be continuously receiving users, you may want to reserve a machine instead of having us manage your compute. + +This will be a flat monthly rate for the machine. + +## Monitoring Usage + +To monitor your projects usage, you can go to the billing tab in the Reflex Cloud UI on the project page. + +Here you can see the current billing and usage for your project. diff --git a/docs/hosting/config_file.md b/docs/hosting/config_file.md new file mode 100644 index 0000000000..5b8c78784c --- /dev/null +++ b/docs/hosting/config_file.md @@ -0,0 +1,32 @@ +```python exec +import reflex as rx +from reflex_image_zoom import image_zoom +from pcweb.pages.docs import hosting +from pcweb.pages import docs +from pcweb.styles.styles import get_code_style, cell_style + +``` +## Config File + +To create a `config.yml` file for your app run the command below: + +```bash +reflex cloud config +``` + +This will create a yaml file similar to the one below where you can edit the app configuration: + +```yaml +name: medo +description: '' +regions: + sjc: 1 + lhr: 2 +vmtype: c1m1 +hostname: null +envfile: .env +project: null +packages: +- procps +``` + diff --git a/docs/hosting/deploy-quick-start.md b/docs/hosting/deploy-quick-start.md index b4a4f4e697..3ae7f8689c 100644 --- a/docs/hosting/deploy-quick-start.md +++ b/docs/hosting/deploy-quick-start.md @@ -72,7 +72,7 @@ If you go back to the Cloud UI you should be able to see your deployed app and o ```md alert info # Setup a Cloud Config File -To create a `config.yml` file for your app to set your app configuration check out the [Cloud Config Docs]({docs.hosting.app.path}#config-file). +To create a `config.yml` file for your app to set your app configuration check out the [Cloud Config Docs]({docs.hosting.config_file.path}). ``` ```md alert info diff --git a/docs/hosting/deploy-with-github-actions.md b/docs/hosting/deploy-with-github-actions.md index de6864d240..49f17fc1ce 100644 --- a/docs/hosting/deploy-with-github-actions.md +++ b/docs/hosting/deploy-with-github-actions.md @@ -37,7 +37,7 @@ github_actions_configs = [ ] ``` -# Reflex Deploy with Github Actions +# Deploy with Github Actions This GitHub Action simplifies the deployment of Reflex applications to Reflex Cloud. It handles setting up the environment, installing the Reflex CLI, and deploying your app with minimal configuration. diff --git a/docs/hosting/logs.md b/docs/hosting/logs.md new file mode 100644 index 0000000000..f0db0ef313 --- /dev/null +++ b/docs/hosting/logs.md @@ -0,0 +1,45 @@ +```python exec +import reflex as rx +from reflex_image_zoom import image_zoom +from pcweb.pages.docs import hosting +from pcweb.pages import docs +from pcweb.styles.styles import get_code_style, cell_style +``` + +## View Logs + +To view the app logs follow the arrow in the image below and press on the `Logs` dropdown. + +```python eval +image_zoom(rx.image(src="/view_logs.webp", padding_bottom="20px")) +``` + +```md alert info +# CLI Command to view logs +`reflex cloud apps logs [OPTIONS] [APP_ID]` +``` + +## View Deployment Logs and Deployment History + +To view the deployment history follow the arrow in the image below and press on the `Deployments`. + +```python eval +image_zoom(rx.image(src="/view_deployment_logs.webp")) +``` + +This brings you to the page below where you can see the deployment history of your app. Click on deployment you wish to explore further. + +```python eval +image_zoom(rx.image(src="/view_deployment_logs_2.webp", padding_bottom="20px")) +``` + +```md alert info +# CLI Command to view deployment history +`reflex cloud apps history [OPTIONS] [APP_ID]` +``` + +This brings you to the page below where you can view the deployment logs of your app by clicking the `Build logs` dropdown. + +```python eval +image_zoom(rx.image(src="/view_deployment_logs_3.webp")) +``` \ No newline at end of file diff --git a/docs/hosting/machine-types.md b/docs/hosting/machine-types.md new file mode 100644 index 0000000000..8eaa355fab --- /dev/null +++ b/docs/hosting/machine-types.md @@ -0,0 +1,34 @@ +```python exec +import reflex as rx +from reflex_image_zoom import image_zoom +from pcweb.pages.docs import hosting +from pcweb.pages import docs +from pcweb.styles.styles import get_code_style, cell_style +``` + +## Machine Types + + +To scale your app you can choose different VMTypes. VMTypes are different configurations of CPU and RAM. + +To scale your VM in the Cloud UI, click on the `Settings` tab in the Cloud UI on the app page, and then click on the `Scale` tab as shown below. Clicking on the `Change VM` button will allow you to scale your app. + +```python eval +image_zoom(rx.image(src="/scaling_vms.webp", padding_bottom="20px")) +``` + +### VMTypes in the CLI + +To get all the possible VMTypes you can run the following command: + +```bash +reflex apps vmtypes +``` + +To set which VMType to use when deploying your app you can pass the `--vmtype` flag with the id of the VMType. For example: + +```bash +reflex deploy --project f88b1574-f101-####-####-5f########## --vmtype c2m4 +``` + +This will deploy your app with the `c2m4` VMType, giving your app 2 CPU cores and 4 GB of RAM. \ No newline at end of file diff --git a/docs/hosting/regions.md b/docs/hosting/regions.md new file mode 100644 index 0000000000..7532cfa3fa --- /dev/null +++ b/docs/hosting/regions.md @@ -0,0 +1,103 @@ +```python exec +import reflex as rx +from reflex_image_zoom import image_zoom +from pcweb.pages.docs import hosting +from pcweb.pages import docs +from pcweb.styles.styles import get_code_style, cell_style + + +regions = { + "ams": "Amsterdam, Netherlands", + "arn": "Stockholm, Sweden", + "atl": "Atlanta, Georgia (US)", + "bog": "Bogotá, Colombia", + "bom": "Mumbai, India", + "bos": "Boston, Massachusetts (US)", + "cdg": "Paris, France", + "den": "Denver, Colorado (US)", + "dfw": "Dallas, Texas (US)", + "ewr": "Secaucus, NJ (US)", + "eze": "Ezeiza, Argentina", + "fra": "Frankfurt, Germany", + "gdl": "Guadalajara, Mexico", + "gig": "Rio de Janeiro, Brazil", + "gru": "Sao Paulo, Brazil", + "hkg": "Hong Kong, Hong Kong", + "iad": "Ashburn, Virginia (US)", + "jnb": "Johannesburg, South Africa", + "lax": "Los Angeles, California (US)", + "lhr": "London, United Kingdom", + "mad": "Madrid, Spain", + "mia": "Miami, Florida (US)", + "nrt": "Tokyo, Japan", + "ord": "Chicago, Illinois (US)", + "otp": "Bucharest, Romania", + "phx": "Phoenix, Arizona (US)", + "qro": "Querétaro, Mexico", + "scl": "Santiago, Chile", + "sea": "Seattle, Washington (US)", + "sin": "Singapore, Singapore", + "sjc": "San Jose, California (US)", + "syd": "Sydney, Australia", + "waw": "Warsaw, Poland", + "yul": "Montreal, Canada", + "yyz": "Toronto, Canada" +} + + +``` + +## Regions + +To scale your app you can choose different regions. Regions are different locations around the world where your app can be deployed. + +To scale your app to multiple regions in the Cloud UI, click on the `Settings` tab in the Cloud UI on the app page, and then click on the `Regions` tab as shown below. Clicking on the `Add new region` button will allow you to scale your app to multiple regions. + +```python eval +image_zoom(rx.image(src="/scaling_regions.webp", padding_bottom="20px")) +``` + +The images below show all the regions that can be deployed in. + +```python eval +rx.hstack( + image_zoom(rx.image(src="/regions_1.webp", padding_bottom="20px")), + image_zoom(rx.image(src="/regions_2.webp", padding_bottom="20px")), +) +``` + +### Selecting Regions to Deploy in the CLI + +Below is an example of how to deploy your app in several regions: + +```bash +reflex deploy --project f88b1574-f101-####-####-5f########## --region sjc --region iad +``` + +By default all apps are deloyed in `sjc` if no other regions are given. If you wish to deploy in another region or several regions you can pass the `--region` flag (`-r` also works) with the region code. Check out all the regions that we can deploy to below: + + +## Config File + +To create a `config.yml` file for your app run the command below: + +```bash +reflex cloud config +``` + +This will create a yaml file similar to the one below where you can edit the app configuration: + +```yaml +name: medo +description: '' +regions: + sjc: 1 + lhr: 2 +vmtype: c1m1 +hostname: null +envfile: .env +project: null +packages: +- procps +``` + diff --git a/pcweb/components/docpage/sidebar/sidebar.py b/pcweb/components/docpage/sidebar/sidebar.py index 5bdb693a1e..aefd4488ec 100644 --- a/pcweb/components/docpage/sidebar/sidebar.py +++ b/pcweb/components/docpage/sidebar/sidebar.py @@ -112,10 +112,13 @@ def sidebar_icon(name): "Database": "database", "Authentication": "lock-keyhole", "Utility Methods": "cog", - "Quick Start": "earth", + "Deploy Quick Start": "earth", "CLI Reference": "square-terminal", + "App": "blocks", + "Project": "server", "Self Hosting": "server", "Custom Components": "blocks", + "Usage": "chart-column", } return ( diff --git a/pcweb/components/docpage/sidebar/sidebar_items/learn.py b/pcweb/components/docpage/sidebar/sidebar_items/learn.py index d28e512f39..db06b73a50 100644 --- a/pcweb/components/docpage/sidebar/sidebar_items/learn.py +++ b/pcweb/components/docpage/sidebar/sidebar_items/learn.py @@ -201,17 +201,37 @@ def get_sidebar_items_hosting(): items = [ create_item( - "Quick Start", + "Deploy Quick Start", children=[ hosting.deploy_quick_start, - hosting.project, - hosting.app, + ], + ), + create_item( + "Project", + children=[ + hosting.adding_members, + ], + ), + create_item( + "App", + children=[ + hosting.app_management, + hosting.machine_types, + hosting.regions, + hosting.logs, hosting.secrets_environment_vars, - hosting.tokens, hosting.custom_domains, - hosting.reflex_branding, + hosting.config_file, + hosting.tokens, hosting.deploy_with_github_actions, ], + ), + create_item( + "Usage", + children=[ + hosting.billing, + hosting.compute, + ] ), create_item( "CLI Reference", diff --git a/pcweb/components/tabs.py b/pcweb/components/tabs.py new file mode 100644 index 0000000000..ad4708e23e --- /dev/null +++ b/pcweb/components/tabs.py @@ -0,0 +1,136 @@ +"""Interactive components provided by base-ui components.""" + +from __future__ import annotations + +from typing import Any, Literal, Optional + +from reflex import Component +from reflex.components.component import ComponentNamespace +from reflex.event import EventHandler +from reflex.vars import Var + + +class BaseUIComponent(Component): + """Set of content sections to be displayed one at a time.""" + + library = "@base-ui-components/react@^1.0.0-alpha.5" + + +class TabsBaseUIComponent(BaseUIComponent): + """Tabs component.""" + + tag = "Tabs" + + +class TabsRoot(TabsBaseUIComponent): + """Groups the tabs and the corresponding panels.""" + + tag = "Tabs.Root" + + # The value of the tab that should be active when initially rendered + default_value: Var[Any] = 0 + + # The controlled value of the tab that should be active + value: Var[Any] + + # Event handler called when the value changes + on_value_change: EventHandler + + # The orientation of the component + orientation: Var[Literal["horizontal", "vertical"]] = "horizontal" + + # Class name + class_name: Optional[Var[str] | str] + + +class TabsList(TabsBaseUIComponent): + """Groups the individual tab buttons.""" + + tag = "Tabs.List" + + # Whether to activate tabs on focus + activate_on_focus: Var[bool] = True + + # Whether to loop through tabs + loop: Var[bool] = True + + # Class name + class_name: Optional[Var[str] | str] = ( + "bg-slate-3 inline-flex gap-1 p-1 items-center justify-start rounded-[10px] relative z-0" + ) + + +class TabsTab(TabsBaseUIComponent): + """An individual interactive tab button.""" + + tag = "Tabs.Tab" + + # The value of the tab + value: Var[Any] + + # Optional icon component + icon: Optional[Component] = None + + # Class name + class_name: Optional[Var[str] | str] = ( + "h-7 px-1.5 rounded-md justify-center items-center gap-1.5 inline-flex text-sm font-medium text-slate-9 cursor-pointer z-[1] hover:text-slate-12 transition-color" + ) + + @classmethod + def create(cls, *children, icon=None, **props) -> Component: + """Create the tab component with optional icon. + + Args: + *children: The children of the component. + icon: Optional icon component to display. + **props: The properties of the component. + + Returns: + The tab Component. + + """ + if icon: + children = (icon, *children) + return super().create(*children, **props) + + +class TabsIndicator(TabsBaseUIComponent): + """Visual indicator for the active tab.""" + + tag = "Tabs.Indicator" + + # Whether to render before hydration + render_before_hydration: Var[bool] = True + + # Class name + class_name: Optional[Var[str] | str] = ( + "absolute top-1/2 left-0 -z-1 h-7 w-[var(--active-tab-width)] -translate-y-1/2 translate-x-[var(--active-tab-left)] rounded-md bg-slate-1 shadow-small transition-all duration-200 ease-in-out" + ) + + +class TabsPanel(TabsBaseUIComponent): + """Content panel associated with a tab.""" + + tag = "Tabs.Panel" + + # The value that associates the panel with a tab + value: Var[Any] + + # Whether to keep the panel mounted when inactive + keep_mounted: Var[bool] = False + + # Class name + class_name: Optional[Var[str] | str] + + +class Tabs(ComponentNamespace): + """Namespace for Tab components.""" + + root = __call__ = staticmethod(TabsRoot.create) + list = staticmethod(TabsList.create) + tab = staticmethod(TabsTab.create) + panel = staticmethod(TabsPanel.create) + indicator = staticmethod(TabsIndicator.create) + + +tabs = Tabs() diff --git a/pcweb/pages/pricing/calculator.py b/pcweb/pages/pricing/calculator.py index 74998c3a30..f8ecf639e0 100644 --- a/pcweb/pages/pricing/calculator.py +++ b/pcweb/pages/pricing/calculator.py @@ -1,318 +1,180 @@ import reflex as rx -from typing import Optional -from reflex.event import EventType, BASE_STATE -from .button import button -from .plan_cards import radial_circle -import enum -MONTH_MINUTES = 60 * 24 * 30 +from pcweb.components.tabs import tabs +COMPUTE_TABLE = { + "c1m.5": {"vcpu": 1, "ram": 0.5, "pph": 0.046}, + "c1m1": {"vcpu": 1, "ram": 1, "pph": 0.083}, + "c1m2": {"vcpu": 1, "ram": 2, "pph": 0.157}, + "c2m2": {"vcpu": 2, "ram": 2, "pph": 0.166}, + "c2m4": {"vcpu": 2, "ram": 4, "pph": 0.312}, + "c4m4": {"vcpu": 4, "ram": 4, "pph": 0.332}, + "c4m8": {"vcpu": 4, "ram": 8, "pph": 0.625}, +} -class Tiers(enum.Enum): - PRO = "Pro" - TEAM = "Team" - -class BillingState(rx.State): - selected_plan: str = Tiers.PRO.value - # Rates - cpu_rate: float = 0.000463 - mem_rate: float = 0.000231 - - # Estimated numbers for the widget calculator - estimated_cpu_number: int = 1 - estimated_ram_gb: int = 1 - estimated_seats: int = 1 - - @rx.var(cache=True) - def seat_rate(self) -> int: - if self.selected_plan == Tiers.PRO.value: - return 20 - elif self.selected_plan == Tiers.TEAM.value: - 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 25 - - @rx.var(cache=True) - def max_cpu(self) -> int: - if self.selected_plan == Tiers.PRO.value: - return 5 - elif self.selected_plan == Tiers.TEAM.value: - return 32 - - @rx.var(cache=True) - def max_ram(self) -> int: - if self.selected_plan == Tiers.PRO.value: - return 10 - elif self.selected_plan == Tiers.TEAM.value: - return 64 - - @rx.event - def change_plan(self, plan: str | list[str]) -> None: - self.selected_plan = plan[0] if isinstance(plan, list) else plan - if plan == Tiers.PRO.value: - self.included_cpu = 1 - self.included_ram = 0.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: +def table_header(cost_text: str) -> rx.Component: return rx.box( - rx.text(text, class_name="text-sm text-slate-12 font-medium text-nowrap"), - rx.box(component, class_name="flex justify-center items-center mx-auto"), - rx.text(total, class_name="text-sm text-slate-9 font-medium text-right"), - class_name="grid grid-cols-3 items-center gap-4", + rx.text("Machine", class_name="text-sm font-semibold text-slate-12"), + rx.text("vCPU", class_name="text-sm font-semibold text-slate-12"), + rx.text("GB RAM", class_name="text-sm font-semibold text-slate-12"), + rx.text(cost_text, class_name="text-sm font-semibold text-slate-12 text-end"), + class_name="grid grid-cols-4 gap-4 px-6 py-3 border-b border-slate-4", ) -def stepper( - value: rx.Var[int], - default_value: str, - min_value: int, - max_value: int, - on_click_decrement: Optional[EventType[[], BASE_STATE]], - on_click_increment: Optional[EventType[[], BASE_STATE]], -) -> rx.Component: +def table_row(name: str, cpu: str, ram: str, cost: str) -> rx.Component: return rx.box( - # Number of seats/cpu/tam rx.box( - rx.el.input( - value=value, - placeholder="0", - default_value=default_value, - min=min_value, - max=max_value, - name="token_days", - on_click=on_click_decrement, - max_length=1000, - class_name="flex flex-row flex-1 gap-2 px-2.5 py-1.5 font-medium text-slate-12 text-sm placeholder:text-slate-9 outline-none focus:outline-none caret-slate-12 absolute left-0 h-full bg-transparent w-[4rem] pointer-events-none", - type="number", - style={ - "appearance": "textfield", - "-webkit-appearance": "textfield", - "-moz-appearance": "textfield", - "&::-webkit-inner-spin-button": {"-webkit-appearance": "none"}, - "&::-webkit-outer-spin-button": {"-webkit-appearance": "none"}, - }, - ), - rx.box( - button( - icon=rx.icon( - "minus", - ), - variant="transparent", - size="icon-xs", - disabled=rx.cond( - value <= min_value, - True, - False, - ), - type="button", - on_click=on_click_decrement, - ), - button( - icon=rx.icon( - "plus", - ), - variant="transparent", - size="icon-xs", - disabled=rx.cond( - value >= max_value, - True, - False, - ), - type="button", - on_click=on_click_increment, - ), - class_name="flex flex-row items-center absolute right-0 border-l border-slate-5 h-full px-1 gap-1", - ), - class_name="!w-[8.5rem] relative border-slate-5 bg-slate-1 border rounded-[0.625rem] h-[2.25rem] flex items-center", + name, + class_name="px-2 w-fit text-slate-12 border-slate-6 h-5 rounded-md border justify-start items-center gap-0.5 inline-flex bg-slate-1 text-sm font-medium shrink-0", ), - class_name="flex flex-row gap-2.5 h-[2.25rem]", + rx.text(cpu, class_name="font-medium text-sm text-slate-9"), + rx.text(ram, class_name="font-medium text-sm text-slate-9"), + rx.text(cost, class_name="font-medium text-sm text-slate-9 text-end"), + class_name="grid grid-cols-4 gap-4 px-6 py-2 border-b border-slate-4 hover:bg-slate-2", ) - -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", - stepper( - BillingState.estimated_seats, - default_value="1", - min_value=1, - max_value=BillingState.max_seats, - on_click_decrement=BillingState.setvar( - "estimated_seats", (BillingState.estimated_seats - 1) - ), - on_click_increment=BillingState.setvar( - "estimated_seats", (BillingState.estimated_seats + 1) - ), - ), - f"${BillingState.estimated_seats * BillingState.seat_rate}", - ), - # GB RAM - calculator( - "GB RAM", - stepper( - BillingState.estimated_ram_gb, - default_value="1", - min_value=1, - max_value=BillingState.max_ram, - on_click_decrement=BillingState.setvar( - "estimated_ram_gb", (BillingState.estimated_ram_gb - 1) - ), - on_click_increment=BillingState.setvar( - "estimated_ram_gb", (BillingState.estimated_ram_gb + 1) - ), - ), - f"${round(BillingState.estimated_ram_gb * (BillingState.mem_rate * MONTH_MINUTES))}", - ), - # CPU - calculator( - "CPU", - stepper( - BillingState.estimated_cpu_number, - default_value="0", - min_value=1, - max_value=BillingState.max_cpu, - on_click_decrement=BillingState.setvar( - "estimated_cpu_number", (BillingState.estimated_cpu_number - 1) - ), - on_click_increment=BillingState.setvar( - "estimated_cpu_number", (BillingState.estimated_cpu_number + 1) - ), - ), - f"${round(BillingState.estimated_cpu_number * (BillingState.cpu_rate * MONTH_MINUTES))}", - ), - class_name="flex flex-col gap-2", - ), - # Total 1 month - rx.center( - rx.flex( - rx.badge( - f"Total: ${calculate_total()}- $20 free credits = ", - rx.text.strong(f"${calculate_total()-20}/mo"), - size="3", - ), - class_name="mt-6", - ) +def learn_more(): + return rx.box( + rx.el.p( + "Learn more about how we calculate ", + rx.el.a("compute costs", href="/docs/hosting/compute", class_name="text-sm font-medium text-slate-10 underline"), + " only when your app is being used.", + class_name="text-sm font-medium text-slate-9" ), - class_name="flex-1 flex flex-col relative h-full w-full max-w-[25rem] pb-2.5 z-[2]", + class_name="px-6 py-4 border-b border-slate-4 hover:bg-slate-2", ) -def calculate_total(): - # 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 +def min_table(cost_text: str, description: bool = False) -> rx.Component: + return rx.box( + table_header(cost_text), + *[ + table_row( + name, + str(specs["vcpu"]), + str(specs["ram"]), + f"${specs['pph'] / 60:.6f}", + ) + for name, specs in COMPUTE_TABLE.items() + ], + learn_more() if description else rx.fragment(), + class_name="w-full", ) - 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) + +def hour_table(cost_text: str, description: bool = False) -> rx.Component: + return rx.box( + table_header(cost_text), + *[ + table_row( + name, + str(specs["vcpu"]), + str(specs["ram"]), + f"${specs['pph']:.3f}", + ) + for name, specs in COMPUTE_TABLE.items() + ], + learn_more() if description else rx.fragment(), + class_name="w-full", + ) - total = base_price + seat_cost + compute_cost - return round(total) -def header() -> rx.Component: +def month_table(cost_text: str) -> rx.Component: return rx.box( - rx.el.h3( - "Cost Estimate", - class_name="text-slate-12 text-3xl font-semibold text-center", - id="calculator-header", - ), - rx.el.p( - "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", + table_header(cost_text), + *[ + table_row( + name, + str(specs["vcpu"]), + str(specs["ram"]), + f"${specs['price_per_min'] * 60 * 24 * 30:.2f}", + ) + for name, specs in COMPUTE_TABLE.items() + ], + class_name="w-full", ) -def filtering_tags(): +def compute_table() -> rx.Component: return rx.box( - # Glow - rx.html( - """ - - - - - - - - -""", - class_name="w-[13.5rem] h-[5.5rem] shrink-0 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-[0] pointer-events-none -mt-2", - ), - rx.box( - rx.segmented_control.root( - rx.segmented_control.item(Tiers.PRO.value, value=Tiers.PRO.value), - rx.segmented_control.item(Tiers.TEAM.value, value=Tiers.TEAM.value), - on_change=BillingState.change_plan, - value=BillingState.selected_plan, - class_name="shadow-large bg-slate-1 rounded-lg border border-slate-3", + tabs.root( + rx.box( + rx.vstack( + rx.el.h3("Compute Pricing", class_name="text-slate-12 text-3xl font-semibold"), + rx.el.p("Only pay when your app is being used, nothing more.", class_name="text-slate-9 text-lg font-semibold"), + align_items="left", + justify_content="center`", + class_name="flex flex-col gap-1", + ), + rx.spacer(), + tabs.list( + tabs.tab( + "Per min", + value="min", + ), + tabs.tab( + "Per hour", + value="hour", + ), + tabs.indicator(), + ), + class_name="flex flex-row gap-2 items-center justify-end p-6 border-b border-slate-4", ), - class_name="mb-5 relative z-[1] overflow-x-auto", + tabs.panel( + min_table("Cost / min", description=True), + value="min", + ), + tabs.panel( + hour_table("Cost / hour", description=True), + value="hour", + ), + default_value="min", ), - class_name="relative", + class_name="flex flex-col w-full mt-5 py-24", ) - -def calculator_section() -> rx.Component: - return rx.el.section( - header(), - rx.box( - radial_circle(), + +def compute_table_base() -> rx.Component: + return rx.box( + tabs.root( rx.box( - rx.flex( - # filtering_tags(), - align_items="center", - justify_content="center", - width="100%", + rx.vstack( + rx.el.h3("Machine Sizes", class_name="text-slate-11 text-lg font-semibold"), + align_items="left", + justify_content="center`", ), - align_items="center", - width="100%", + rx.spacer(), + tabs.list( + tabs.tab( + "Per min", + value="min", + ), + tabs.tab( + "Per hour", + value="hour", + ), + tabs.indicator(), + ), + class_name="flex flex-row gap-2 items-center justify-end pb-6 border-b border-slate-4", ), - rx.box(pricing_widget()), - class_name="flex flex-col p-8 border border-slate-4 rounded-[1.125rem] shadow-small bg-slate-2 relative z-[1]", + tabs.panel( + min_table("Cost / min"), + value="min", + ), + tabs.panel( + hour_table("Cost / hour"), + value="hour", + ), + default_value="min", ), + class_name="flex flex-col w-full py-6", + ) + +def calculator_section() -> rx.Component: + return rx.el.section( + compute_table(), class_name="flex flex-col w-full max-w-[64.19rem] 2xl:border-x border-slate-4 2xl:border-b pb-[6rem] justify-center items-center", ) diff --git a/pcweb/pages/pricing/faq.py b/pcweb/pages/pricing/faq.py index 748dd0312b..04c6bd62fd 100644 --- a/pcweb/pages/pricing/faq.py +++ b/pcweb/pages/pricing/faq.py @@ -84,8 +84,8 @@ def accordion_text(text: str) -> rx.Component: "Yes! Reflex is open source and free to use. You can self-host your apps or use our hosting platform which has a generous free tier.", ), ( - "What is the difference between Reflex and Reflex Hosting?", - "Reflex is our open-source framework for building web apps. Reflex Hosting is our platform for hosting Reflex apps.", + "What is the difference between Reflex and Reflex Cloud?", + "Reflex is our open-source framework for building web apps. Reflex Cloud is our platform for hosting Reflex apps.", ), ( "Why was I charged $1 after adding a credit card?", @@ -93,7 +93,7 @@ def accordion_text(text: str) -> rx.Component: ), ( "How usage based billing is calculated?", - "Usage is calculated based on compute resources (CPU/RAM) consumed by your app. We measure this in compute units per hour.", + "Usage is calculated based on compute resources (CPU/RAM) consumed by your app. We measure this in compute units per minute.", ), ( "What happens when I upgrade?", @@ -113,7 +113,7 @@ def accordion_text(text: str) -> rx.Component: ), ( "Can I add members to my project?", - "Yes! Pro plan allows up to 5 team members, Team plan up to 15 members, and Enterprise plan have unlimited team members.", + "Yes! You can add unlimited members to your project with a per seat monthly charge on additional seats.", ), ] @@ -129,5 +129,5 @@ def faq() -> rx.Component: class_name="max-w-[40rem] flex justify-center items-center flex-col mx-auto w-full gap-2", ), sales_button(), - class_name="flex flex-col gap-8 w-full max-w-[64.19rem] 2xl:border-x border-slate-4 2xl:border-b pb-[6rem] pt-16", + class_name="flex flex-col gap-8 w-full max-w-[64.19rem] 2xl:border-x border-slate-4 2xl:border-b pb-[6rem] py-32", ) diff --git a/pcweb/pages/pricing/plan_cards.py b/pcweb/pages/pricing/plan_cards.py index 47d30df764..e0f85b427a 100644 --- a/pcweb/pages/pricing/plan_cards.py +++ b/pcweb/pages/pricing/plan_cards.py @@ -31,7 +31,7 @@ def glow() -> rx.Component: - + @@ -78,19 +78,19 @@ def grid() -> rx.Component: - + - + - + - + - + @@ -137,10 +137,10 @@ def card( title: str, description: str, features: list[tuple[str, str]], button_text: str, price: str = None ) -> rx.Component: return rx.box( - rx.hstack( - rx.el.h3(title, class_name="font-semibold text-slate-12 text-2xl mb-2"), + rx.el.div( + rx.el.h3(title, class_name="font-semibold text-slate-12 text-2xl"), rx.badge(price, color_scheme="gray", size="3") if price else rx.fragment(), - align_items="center", + class_name="flex items-center mb-2 gap-2", ), rx.el.p( description, class_name="text-sm font-medium text-slate-9 mb-8 text-pretty" @@ -188,9 +188,9 @@ def popular_card( glow(), grid(), rx.hstack( - rx.el.h3(title, class_name="font-semibold text-slate-12 text-2xl mb-2"), + rx.el.h3(title, class_name="font-semibold text-slate-12 text-2xl"), rx.badge(price, color_scheme="violet", size="3") if price else rx.fragment(), - align_items="center", + class_name="flex items-center mb-2 gap-2", ), rx.el.p(description, class_name="text-sm font-medium text-slate-9 mb-8"), rx.el.ul( @@ -233,7 +233,7 @@ def plan_cards() -> rx.Component: "Everything you need to get started.", [ ("heart-handshake", "Community support"), - ("app-window", "1 app included", "Free apps shutdown after 1 hour of inactivity, and are restarted on demand (usually within a few seconds)."), + ("app-window", "Unlimited apps", "Free users are limited to 20 hours of 1 vCPU, 1 GB RAM machines per month."), ("clock", "1 day log retention"), ("code", "Open Source Framework"), ("file-code", "Starter Templates"), @@ -245,15 +245,15 @@ def plan_cards() -> rx.Component: "Pro", "For professional projects and startups.", [ - ("heart-handshake", "Community support"), + ("heart-handshake", "Priority 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"), + ("credit-card", "$10 / month free compute credits"), + ("server", "Customize machine sizes"), ("clock", "30 days log retention"), ("globe", "Multi-region"), ("brush", "Custom domains"), - ("file-badge", "Remove Branding with Reflex Cloud", "Check out the Reflex Branding doc page to learn how to remove the 'Made in Reflex' badge from your app.") + ("file-badge", "Remove Branding", "Check out the Reflex Branding doc page to learn how to remove the 'Made in Reflex' badge from your app."), + ("circle-plus", "Everything in Hobby"), ], "Start with Pro plan", price="$20/mo + compute", @@ -265,11 +265,10 @@ def plan_cards() -> rx.Component: ("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"), - ("file-badge", "Remove Branding for Self-Hosting", "Check out the Remove Reflex Branding section in the Self Hosting doc page to learn how to remove the 'Made in Reflex' badge from your app."), + ("file-badge", "Remove Branding Everywhere", "Check out the Remove Reflex Branding section in the Self Hosting doc page to learn how to remove the 'Made in Reflex' badge from your app."), ("circle-plus", "Everything in Pro"), ], "Contact sales", @@ -278,7 +277,7 @@ def plan_cards() -> rx.Component: "Enterprise", "Get a plan tailored to your business needs.", [ - ("headset", "Priority Engineering Support"), + ("headset", "Dedicated Support"), ("users", "Customized seat amount"), ("user-round-plus", "Personalized integration help"), ("hard-drive", "On Premise Deployment"), diff --git a/pcweb/pages/pricing/pricing.py b/pcweb/pages/pricing/pricing.py index bb85450f4f..eaa8696750 100644 --- a/pcweb/pages/pricing/pricing.py +++ b/pcweb/pages/pricing/pricing.py @@ -23,6 +23,7 @@ def pricing() -> rx.Component: rx.box( header(), plan_cards(), + calculator_section(), comparison_table_hosting(), comparison_table_oss(), faq(), diff --git a/pcweb/pages/pricing/table.py b/pcweb/pages/pricing/table.py index 52cf9d76de..b740ee584b 100644 --- a/pcweb/pages/pricing/table.py +++ b/pcweb/pages/pricing/table.py @@ -40,12 +40,10 @@ ] REFLEX_BRANDING_SECTION = [ - ("Remove Reflex Branding", "", "On Reflex Cloud", "Everywhere *", "Everywhere *"), + ("Remove Branding", "", "On Cloud", "Everywhere*", "Everywhere*"), ] -REFLEX_AI_SECTION = [ - ("Number of Generations", "5/month", "100/month/seat", "250/month/seat", "Custom"), -] +REFLEX_AI_SECTION = [] HOSTING_TEXT_SECTION = [ ("Regions", "Single", "Multiple", "Multiple", "Multiple"), @@ -54,7 +52,8 @@ HOSTING_BOOLEAN_SECTION = [ ("CLI Deployments", True, True, True, True), - ("Automatic CI / CD Deploy (Github)", True, True, True, True), + ("CI/CD Deploy Tokens", True, True, True, True), + ("Set Billing Limits", True, True, True, True), ("Custom Domains", False, True, True, True), ("Secret Manager", False, True, True, True), ("App Analytics", False, True, True, True), @@ -75,7 +74,7 @@ ] SUPPORT_BOOLEAN_SECTION = [ - ("Support SLAs Available", False, False, False, True), + ("SLAs Available", False, False, False, True), ("Personalized Onboarding", False, False, False, True), ("", "", "", "", ""), ] @@ -88,7 +87,8 @@ ] ASTERIX_SECTION = [ - ("* Everywhere: This includes removing the 'Made in Reflex' badge for self hosted apps.", "", "", "", "") + ("* Everywhere: This includes removing the 'Made in Reflex' badge for self hosted apps.", "", "", "", ""), + ("", "", "", "", ""), ] def glow() -> rx.Component: @@ -132,14 +132,21 @@ 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-[50px]", + class_name="w-full [&>*:not(:first-child)]:text-center bg-slate-1 z-[2] !h-[50px] hover:bg-slate-2", ) def create_table_row_header(name: list, coming_soon: bool = False) -> rx.Component: return rx.table.row( *[ - 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( + rx.el.div( + name, + rx.badge("coming soon", margin_left="0.5rem"), + class_name="flex items-center gap-2" + ), + 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"]), @@ -242,13 +249,6 @@ def table_body_hosting() -> rx.Component: def table_body_oss() -> rx.Component: return rx.table.root( - rx.table.header( - create_table_row_header("AI", coming_soon=True), - class_name="relative", - ), - create_table_body( - *[create_table_row(row) for row in REFLEX_AI_SECTION], - ), rx.table.header( create_table_row_header("Framework"), class_name="relative", diff --git a/pcweb/whitelist.py b/pcweb/whitelist.py index 581d2e6ec0..8ead1258da 100644 --- a/pcweb/whitelist.py +++ b/pcweb/whitelist.py @@ -9,9 +9,9 @@ # - Correct: WHITELISTED_PAGES = ["/docs/getting-started/introduction"] # - Incorrect: WHITELISTED_PAGES = ["/docs/getting-started/introduction/"] -WHITELISTED_PAGES = [] - +WHITELISTED_PAGES = [] + def _check_whitelisted_path(path): if len(WHITELISTED_PAGES) == 0: return True