diff --git a/assets/hosting/dark/hosting-preview.jpg b/assets/hosting/dark/hosting-preview.jpg
new file mode 100644
index 0000000000..c44e4f458d
Binary files /dev/null and b/assets/hosting/dark/hosting-preview.jpg differ
diff --git a/assets/hosting/dark/hosting_patterns.svg b/assets/hosting/dark/hosting_patterns.svg
new file mode 100644
index 0000000000..b0242c6700
--- /dev/null
+++ b/assets/hosting/dark/hosting_patterns.svg
@@ -0,0 +1,507 @@
+
diff --git a/assets/hosting/light/hosting-preview.jpg b/assets/hosting/light/hosting-preview.jpg
new file mode 100644
index 0000000000..e5b05244a8
Binary files /dev/null and b/assets/hosting/light/hosting-preview.jpg differ
diff --git a/assets/hosting/light/hosting_patterns.svg b/assets/hosting/light/hosting_patterns.svg
new file mode 100644
index 0000000000..b97bf86ec5
--- /dev/null
+++ b/assets/hosting/light/hosting_patterns.svg
@@ -0,0 +1,507 @@
+
diff --git a/pcweb/components/docpage/navbar/navbar.py b/pcweb/components/docpage/navbar/navbar.py
index f8c34a079f..0a3f2a99b5 100644
--- a/pcweb/components/docpage/navbar/navbar.py
+++ b/pcweb/components/docpage/navbar/navbar.py
@@ -323,6 +323,9 @@ def new_component_section() -> rx.Component:
"none",
),
),
+ nav_menu.item(
+ link_item("Hosting", "/hosting", "hosting"),
+ ),
nav_menu.item(
link_item("Pricing", "/pricing", "pricing"),
),
diff --git a/pcweb/components/icons/icons.py b/pcweb/components/icons/icons.py
index 54e5237ec9..fcc8b1da14 100644
--- a/pcweb/components/icons/icons.py
+++ b/pcweb/components/icons/icons.py
@@ -379,6 +379,21 @@
"""
+infinity = """
+"""
+
+analytics = """"""
+
+globe = """"""
+
ICONS = {
# Socials
"github": github,
@@ -427,6 +442,9 @@
"python": python,
"package": package,
"document_code": document_code,
+ "infinity": infinity,
+ "analytics": analytics,
+ "globe": globe,
}
LiteralIcon = Literal[
@@ -474,7 +492,10 @@
"forum",
"python",
"package",
- "document_code"
+ "document_code",
+ "infinity",
+ "analytics",
+ "globe",
]
diff --git a/pcweb/components/icons/patterns.py b/pcweb/components/icons/patterns.py
index c2a2215e4f..2029287255 100644
--- a/pcweb/components/icons/patterns.py
+++ b/pcweb/components/icons/patterns.py
@@ -1,6 +1,6 @@
import reflex as rx
from pcweb.components.icons.icons import get_icon
-
+from pcweb.components.hosting_banner import HostingBannerState
def create_pattern(
pattern: str,
@@ -13,6 +13,7 @@ def create_pattern(
+ class_name,
)
+
def default_patterns() -> rx.Component:
return [
# Left
@@ -39,6 +40,7 @@ def default_patterns() -> rx.Component:
),
]
+
def index_patterns() -> rx.Component:
return [
rx.box(
@@ -55,3 +57,21 @@ def index_patterns() -> rx.Component:
class_name="bg-[radial-gradient(50%_50%_at_50%_50%,_var(--glow)_0%,_rgba(21,_22,_24,_0.00)_100%)] w-[56.125rem] h-[11.625rem] rounded-[56.125rem] overflow-hidden pointer-events-none shrink-0 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-[-1] mt-[65.75rem] absolute top-0"
),
]
+
+
+def hosting_patterns() -> rx.Component:
+ return [
+ rx.image(
+ src=rx.color_mode_cond(
+ light="/hosting/light/hosting_patterns.svg",
+ dark="/hosting/dark/hosting_patterns.svg",
+ ),
+ alt="Reflex Hosting Patterns",
+ class_name="desktop-only absolute top-0 z-[-1] w-[1028px] h-[478px] pointer-events-none shrink-0 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
+ + rx.cond(
+ HostingBannerState.show_banner,
+ " lg:mt-[24rem] mt-[3.5rem]",
+ " lg:mt-[19rem] mt-[8.5rem]",
+ ),
+ )
+ ]
diff --git a/pcweb/components/new_button.py b/pcweb/components/new_button.py
new file mode 100644
index 0000000000..6ed9c07f1f
--- /dev/null
+++ b/pcweb/components/new_button.py
@@ -0,0 +1,92 @@
+from typing import Any, Dict, Literal, Optional
+
+import reflex as rx
+
+LiteralButtonVariant = Literal[
+ "primary", "secondary", "transparent", "destructive", "outline"
+]
+LiteralButtonSize = Literal["sm", "md", "lg", "xl", "icon-sm", "icon-md", "icon-lg", "icon-xl"]
+
+DEFAULT_CLASS_NAME = "text-sm cursor-pointer inline-flex items-center justify-center relative transition-bg shrink-0 font-sans disabled:cursor-not-allowed disabled:border disabled:border-slate-5 disabled:!bg-slate-3 disabled:!text-slate-8 transition-bg"
+
+
+def get_variant_bg_cn(variant: str) -> str:
+ """Get the background color class name for a button variant.
+
+ Args:
+ variant (str): The variant of the button.
+
+ Returns:
+ str: The background color class name.
+
+ """
+ return f"enabled:bg-gradient-to-b from-[--{variant}-9] to-[--{variant}-10] dark:to-[--{variant}-9] hover:to-[--{variant}-9] dark:hover:to-[--{variant}-10] disabled:hover:bg-[--{variant}-9]"
+
+
+BUTTON_STYLES: Dict[str, Dict[str, Dict[str, str]]] = {
+ "size": {
+ "xs": "px-1.5 h-7 rounded-md gap-1.5",
+ "sm": "px-2 h-8 rounded-lg gap-2",
+ "md": "px-2.5 h-9 rounded-[10px] gap-2.5",
+ "lg": "px-3 h-10 rounded-xl gap-3",
+ "xl": "px-3.5 h-12 rounded-[14px] gap-3.5 !text-base text-nowrap",
+ "icon-xs": "size-7 rounded-md",
+ "icon-sm": "size-8 rounded-lg",
+ "icon-md": "size-9 rounded-[10px]",
+ "icon-lg": "size-10 rounded-md",
+ "icon-xl": "size-12 rounded-[14px]",
+ },
+ "variant": {
+ "primary": lambda: f"{get_variant_bg_cn('violet')} text-[#FCFCFD] font-semibold",
+ "secondary": "bg-slate-4 hover:bg-slate-5 text-slate-11 font-semibold",
+ "transparent": "bg-transparent hover:bg-slate-3 text-slate-9 font-medium",
+ "destructive": lambda: f"{get_variant_bg_cn('red')} text-[#FCFCFD] font-semibold",
+ "outline": "bg-slate-1 hover:bg-slate-3 text-slate-9 font-medium border border-slate-5",
+ },
+}
+
+
+def button(
+ text: str = "",
+ variant: LiteralButtonVariant = "primary",
+ size: LiteralButtonSize = "sm",
+ style: Dict[str, Any] = None,
+ class_name: str = "",
+ icon: Optional[rx.Component] = None,
+ **props,
+) -> rx.Component:
+ """Create a button component.
+
+ Args:
+ text (str): The text to display on the button.
+ variant (LiteralButtonVariant, optional): The button variant. Defaults to "primary".
+ size (LiteralButtonSize, optional): The button size. Defaults to "sm".
+ style (Dict[str, Any], optional): Additional styles to apply to the button. Defaults to {}.
+ class_name (str, optional): Additional CSS classes to apply to the button. Defaults to "".
+ icon (Optional[rx.Component], optional): An optional icon component to display before the text. Defaults to None.
+ **props: Additional props to pass to the button element.
+
+ Returns:
+ rx.Component: A button component with the specified properties.
+
+ """
+ if style is None:
+ style = {}
+ variant_class = BUTTON_STYLES["variant"][variant]
+ variant_class = variant_class() if callable(variant_class) else variant_class
+
+ classes = [
+ DEFAULT_CLASS_NAME,
+ BUTTON_STYLES["size"][size],
+ variant_class,
+ class_name,
+ ]
+
+ content = [icon, text] if icon else [text]
+
+ return rx.el.button(
+ *content,
+ style=style,
+ class_name=" ".join(filter(None, classes)),
+ **props,
+ )
diff --git a/pcweb/constants.py b/pcweb/constants.py
index 1b8d8c2490..39b56bc660 100644
--- a/pcweb/constants.py
+++ b/pcweb/constants.py
@@ -30,7 +30,7 @@
GALLERY_FORM_URL = "https://docs.google.com/forms/d/e/1FAIpQLSfB30hXB09CZ_H0Zi684w1y1zQSScyT3Qhd1mOUrAAIq9dj3Q/viewform?usp=sf_link"
NPMJS_URL = "https://www.npmjs.com/"
SPLINE_URL = "https://github.com/splinetool/react-spline"
-HOSTING_URL = "https://cloud.staging.reflexcorp.run"
+HOSTING_URL = "https://cloud.reflexcorp.run"
# Install urls.
BUN_URL = "https://bun.sh"
diff --git a/pcweb/pages/__init__.py b/pcweb/pages/__init__.py
index 39c573f208..b48db533f7 100644
--- a/pcweb/pages/__init__.py
+++ b/pcweb/pages/__init__.py
@@ -11,9 +11,11 @@
from .customers.landing import customers
from .customers.data.customers import customers_routes
from .gallery.apps import gallery_apps_routes
-from .hosting_countdown.hosting_countdown import hosting_countdown
+# from .hosting_countdown.hosting_countdown import hosting_countdown
from .pricing.pricing import pricing
from .sales import sales
+from .hosting.hosting import hosting_landing
+
routes = [
*[r for r in locals().values() if isinstance(r, Route) and r.add_as_page],
*blog_routes,
diff --git a/pcweb/pages/hosting/hosting.py b/pcweb/pages/hosting/hosting.py
new file mode 100644
index 0000000000..ed189d0103
--- /dev/null
+++ b/pcweb/pages/hosting/hosting.py
@@ -0,0 +1,40 @@
+import reflex as rx
+
+from .views.hero import hero
+from .views.preview import preview
+from .views.deploy_animation import deploy_animation
+from .views.pricing_cards import pricing_cards
+from .views.templates import templates
+from .views.features import features
+from pcweb.pages.index.views.companies import companies
+from pcweb.components.icons.patterns import hosting_patterns
+from pcweb.components.docpage.navbar import navbar
+from pcweb.pages.index.views.get_started import get_started
+from pcweb.pages.index.views.footer_index import footer_index
+from pcweb.components.webpage.badge import badge
+from pcweb.pages.index.index_colors import index_colors
+from pcweb.meta.meta import meta_tags
+
+
+@rx.page(route="/hosting", title="Reflex ยท Hosting", meta=meta_tags)
+def hosting_landing() -> rx.Component:
+ """Get the main Reflex landing page."""
+ return rx.box(
+ index_colors(),
+ *hosting_patterns(),
+ navbar(),
+ rx.el.main(
+ hero(),
+ preview(),
+ # companies(),
+ deploy_animation(),
+ features(),
+ pricing_cards(),
+ templates(),
+ get_started(),
+ class_name="flex flex-col w-full justify-center items-center",
+ ),
+ footer_index(),
+ badge(),
+ class_name="flex flex-col w-full max-w-[94.5rem] justify-center items-center mx-auto px-4 lg:px-5 relative overflow-hidden",
+ )
diff --git a/pcweb/pages/hosting/views/deploy_animation.py b/pcweb/pages/hosting/views/deploy_animation.py
new file mode 100644
index 0000000000..2d1adfa8bc
--- /dev/null
+++ b/pcweb/pages/hosting/views/deploy_animation.py
@@ -0,0 +1,153 @@
+import reflex as rx
+from pcweb.pages.hosting_countdown.animated_box import (
+ terminal_box,
+ deploy_box,
+ typing_text_script,
+)
+
+
+def grid() -> rx.Component:
+ return rx.html(
+ """
+""",
+ class_name="shrink-0 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-[-2] pointer-events-none w-full h-full",
+ )
+
+
+def animated_box() -> rx.Component:
+ return rx.box(
+ rx.box(
+ terminal_box(),
+ deploy_box(),
+ # Glow
+ rx.html(
+ """
+
+ """,
+ class_name="absolute bottom-[-6.5rem] left-1/2 transform -translate-x-1/2 w-[26.625rem] h-[6rem] flex-shrink-0 pointer-events-none",
+ ),
+ class_name="justify-center flex flex-col items-center max-w-[34.5rem] max-h-[17.875rem] shrink-0 relative w-full h-full overflow-hidden",
+ ),
+ on_mount=rx.call_script(typing_text_script()), # On dev it will run twice
+ class_name="flex items-center justify-center w-full h-[6rem] mx-auto",
+ )
+
+
+def deploy_animation() -> rx.Component:
+ return rx.el.section(
+ grid(),
+ rx.box(
+ rx.el.h2(
+ "Deploy, manage, and scale.",
+ class_name="lg:text-3xl text-xl font-semibold text-slate-12 text-balance",
+ ),
+ rx.el.span(
+ "A complete infrastructure for your apps",
+ class_name="lg:text-3xl text-xl font-semibold text-slate-9 z-[1] text-balance",
+ ),
+ class_name="flex flex-col text-center",
+ ),
+ animated_box(),
+ class_name="overflow-hidden flex flex-col justify-center gap-[3.5rem] w-full h-auto max-w-[64.19rem] lg:border-x border-slate-3 lg:px-[8.5rem] lg:pt-[5.5rem] pt-12 border-b border-t relative",
+ )
diff --git a/pcweb/pages/hosting/views/features.py b/pcweb/pages/hosting/views/features.py
new file mode 100644
index 0000000000..d09d905aca
--- /dev/null
+++ b/pcweb/pages/hosting/views/features.py
@@ -0,0 +1,178 @@
+import reflex as rx
+from pcweb.components.icons import get_icon
+from pcweb.components.new_button import button
+from pcweb.constants import HOSTING_URL
+
+
+def grid() -> rx.Component:
+ return rx.html(
+ """
+""",
+ class_name="shrink-0 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-[-2] pointer-events-none w-full h-full ml-[-2px] mt-[1px]",
+ )
+
+
+def feature_card(icon: str, title: str, description: str) -> rx.Component:
+ return rx.box(
+ rx.box(
+ rx.box(
+ get_icon(icon, class_name="!text-slate-12 !size-5"),
+ rx.text(title, class_name="text-slate-12 text-base font-medium"),
+ class_name="flex flex-row gap-2 items-center",
+ ),
+ rx.text(
+ description, class_name="text-slate-9 font-medium text-sm text-start"
+ ),
+ class_name="flex flex-col gap-2 w-[22rem] h-[8rem] px-8 py-5",
+ ),
+ class_name="border-slate-3 border-b box-border",
+ )
+
+
+def features() -> rx.Component:
+ return rx.el.section(
+ # TODO: Fill with updated comments
+ grid(),
+ rx.box(
+ rx.box(
+ feature_card(
+ "backend_db",
+ "Build and deploy",
+ "Skip the boilerplate and get started faster. No need to write API endpoints.",
+ ),
+ feature_card(
+ "infinity",
+ "Integrate with CI/CD",
+ "Skip the boilerplate and get started faster. No need to write API endpoints.",
+ ),
+ feature_card(
+ "globe",
+ "Scale to multiple regions",
+ "Skip the boilerplate and get started faster. No need to write API endpoints.",
+ ),
+ rx.box(class_name="h-[4rem]"),
+ class_name="flex flex-col pt-8 lg:border-r border-slate-3",
+ ),
+ rx.box(
+ feature_card(
+ "backend_auth",
+ "Add team members",
+ "Skip the boilerplate and get started faster. No need to write API endpoints.",
+ ),
+ feature_card(
+ "analytics",
+ "Get alerts and metrics",
+ "Skip the boilerplate and get started faster. No need to write API endpoints.",
+ ),
+ class_name="flex flex-col -mt-[32px]",
+ ),
+ class_name="flex lg:flex-row flex-col justify-center items-center",
+ ),
+ rx.link(
+ button("Start building", variant="primary", size="xl"),
+ href=HOSTING_URL,
+ is_external=True,
+ class_name="p-2 border border-slate-3 rounded-[1.375rem] border-solid lg:mt-0 mt-4",
+ ),
+ class_name="flex flex-col justify-center items-center max-w-[64.19rem] lg:border-x border-slate-3 w-full mx-auto lg:pb-[5.5rem] pb-4 relative overflow-hidden",
+ )
diff --git a/pcweb/pages/hosting/views/hero.py b/pcweb/pages/hosting/views/hero.py
new file mode 100644
index 0000000000..f9e80a23ad
--- /dev/null
+++ b/pcweb/pages/hosting/views/hero.py
@@ -0,0 +1,53 @@
+import reflex as rx
+from pcweb.components.new_button import button
+from pcweb.constants import REFLEX_DEV_WEB_LANDING_FORM_URL_GET_DEMO
+from pcweb.constants import HOSTING_URL
+from pcweb.components.hosting_banner import HostingBannerState
+
+
+def hero() -> rx.Component:
+ """Render the hero section of the landing page."""
+ return rx.el.section(
+ # Headings
+ rx.el.h1(
+ "Deploy with a single command",
+ class_name="max-w-full inline-block bg-clip-text bg-gradient-to-r from-slate-12 to-slate-11 w-full text-4xl lg:text-5xl text-center text-transparent text-balance mx-auto break-words font-semibold",
+ ),
+ # TODO: Change this wording
+ rx.el.h2(
+ """A unified platform to build and manage your Reflex apps""",
+ class_name="max-w-full w-full font-large text-center text-slate-11 -mt-2 font-normal text-[1.25rem] mx-auto text-balance word-wrap break-words md:whitespace-pre",
+ ),
+ # Buttons
+ rx.box(
+ rx.link(
+ button(
+ "Start building",
+ size="xl",
+ class_name="w-full lg:w-fit",
+ ),
+ underline="none",
+ href=HOSTING_URL,
+ class_name="w-full lg:w-fit",
+ ),
+ rx.link(
+ button(
+ "Get a demo",
+ size="xl",
+ variant="secondary",
+ class_name="lg:!w-[8.25rem] w-full",
+ ),
+ href=REFLEX_DEV_WEB_LANDING_FORM_URL_GET_DEMO,
+ is_external=True,
+ underline="none",
+ class_name="w-full lg:w-fit",
+ ),
+ class_name="flex flex-col lg:flex-row items-center gap-4 mt-4 w-full lg:w-auto lg:max-w-full max-w-[24rem]",
+ ),
+ class_name="flex flex-col justify-center items-center gap-4 mx-auto w-full max-w-[64.19rem] lg:border-x border-slate-3 pb-4 lg:pb-[7.875rem]"
+ + rx.cond(
+ HostingBannerState.show_banner,
+ " lg:pt-[18.2rem] pt-[11rem]",
+ " lg:pt-[13.2rem] pt-[6rem]",
+ ),
+ )
diff --git a/pcweb/pages/hosting/views/preview.py b/pcweb/pages/hosting/views/preview.py
new file mode 100644
index 0000000000..230adedda7
--- /dev/null
+++ b/pcweb/pages/hosting/views/preview.py
@@ -0,0 +1,38 @@
+import reflex as rx
+
+
+def pattern_1() -> rx.Component:
+ return rx.html(
+ """
+
+
+ """,
+ class_name="shrink-0 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-[-2] pointer-events-none",
+ )
+
+
+def preview() -> rx.Component:
+ return rx.box(
+ pattern_1(),
+ rx.image(
+ src=rx.color_mode_cond(
+ light="/hosting/light/hosting-preview.jpg",
+ dark="/hosting/dark/hosting-preview.jpg",
+ ),
+ alt="Reflex Hosting Preview",
+ class_name="w-full h-full rounded-lg lg:rounded-2xl object-cover bg-center border border-slate-3",
+ loading="lazy",
+ ),
+ class_name="w-full h-full max-w-[71rem] max-h-[41rem] rounded-[2rem] backdrop-blur-[6px] bg-[rgba(26, 27, 29, 0.48)] lg:p-4 lg:border border-slate-3 relative lg:mt-0 mt-[3.5rem]",
+ )
diff --git a/pcweb/pages/hosting/views/pricing_cards.py b/pcweb/pages/hosting/views/pricing_cards.py
new file mode 100644
index 0000000000..19e5f47e7b
--- /dev/null
+++ b/pcweb/pages/hosting/views/pricing_cards.py
@@ -0,0 +1,34 @@
+import reflex as rx
+from pcweb.pages.pricing.plan_cards import plan_cards
+from pcweb.components.new_button import button
+
+
+def pricing_cards() -> rx.Component:
+ return rx.el.section(
+ rx.box(
+ rx.el.h2(
+ "Start for free and scale as you grow.",
+ class_name="lg:text-3xl text-xl font-semibold text-slate-12 text-balance",
+ ),
+ rx.el.span(
+ "Flexible pricing",
+ class_name="lg:text-3xl text-xl font-semibold text-slate-9 z-[1] text-balance",
+ ),
+ class_name="flex flex-col text-center pb-[3.5rem] max-w-[64.19rem] lg:border-x border-slate-3 border-t pt-[5rem] w-full mx-auto",
+ ),
+ plan_cards(),
+ rx.box(
+ rx.link(
+ button(
+ "Compare plans",
+ size="md",
+ icon=rx.icon("chevron-right", size=16),
+ variant="transparent",
+ class_name="mt-[3.5rem] flex-row-reverse mb-[5rem]",
+ ),
+ href="/pricing",
+ ),
+ class_name="flex flex-col text-center max-w-[64.19rem] lg:border-x border-slate-3 w-full mx-auto",
+ ),
+ class_name="flex flex-col justify-center items-center w-full relative",
+ )
diff --git a/pcweb/pages/hosting/views/templates.py b/pcweb/pages/hosting/views/templates.py
new file mode 100644
index 0000000000..de0730ad57
--- /dev/null
+++ b/pcweb/pages/hosting/views/templates.py
@@ -0,0 +1,86 @@
+import reflex as rx
+
+
+def gallery_app_card(app: dict) -> rx.Component:
+ return rx.box(
+ rx.box(
+ rx.link(
+ rx.image(
+ src=app["image"],
+ loading="lazy",
+ alt="Image preview for app: " + app["title"],
+ class_name="w-full h-full duration-150 object-top object-cover hover:scale-105 transition-transform ease-out",
+ ),
+ href=f"/templates/{app['url'].replace(' ', '-').lower()}",
+ ),
+ class_name="relative border-slate-5 border-b border-solid w-full overflow-hidden h-[11.5rem]",
+ ),
+ rx.box(
+ rx.text(
+ app["title"],
+ class_name="font-smbold text-slate-12 truncate",
+ width="100%",
+ ),
+ rx.text(
+ app["description"],
+ class_name="text-slate-10 font-small truncate text-pretty",
+ width="100%",
+ ),
+ class_name="flex flex-col items-start gap-2 p-6 w-full h-auto",
+ ),
+ class_name="box-border flex flex-col border-slate-4 bg-slate-2 shadow-large border rounded-xl w-full h-auto overflow-hidden",
+ )
+
+
+templates_name_map = {
+ "dalle": "DALL-E",
+ "sales": "Sales App",
+ "nba": "NBA App",
+ "reflex-llamaindex-template": "LLamaIndex App",
+ "ai_image_gen": "AI Image Gen",
+ "dashboard": "Dashboard",
+ "customer_data_app": "Customer Data App",
+ "ci_template": "CI/CD Template",
+ "reflex-chat": "Chat App",
+ "api_admin_panel": "API Admin Panel",
+}
+
+
+def component_grid() -> rx.Component:
+ from pcweb.pages.gallery.apps import gallery_apps_data
+
+ posts = []
+ for path, document in list(gallery_apps_data.items()):
+ document.metadata["url"] = document.metadata["title"]
+ document.metadata["title"] = templates_name_map.get(
+ document.metadata["title"], document.metadata["title"]
+ )
+ # Skip the DALL-E template
+ if document.metadata["title"] == "DALL-E":
+ continue
+ posts.append(gallery_app_card(app=document.metadata))
+ return rx.box(
+ *posts,
+ class_name="gap-6 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 [&>*]:min-w-[22.5rem] w-full",
+ )
+
+
+def templates() -> rx.Component:
+ return rx.el.section(
+ rx.box(
+ rx.el.h2(
+ "Quickly start with a template.",
+ class_name="lg:text-3xl text-xl font-semibold text-slate-12 text-balance",
+ ),
+ rx.el.span(
+ "Select one to continue",
+ class_name="lg:text-3xl text-xl font-semibold text-slate-9 z-[1] text-balance",
+ ),
+ class_name="flex flex-col text-center pb-[3rem] max-w-[64.19rem] lg:border-x border-slate-3 border-t pt-[5rem] w-full mx-auto",
+ ),
+ component_grid(),
+ rx.box(
+ class_name="flex flex-col text-center pb-[3rem] max-w-[64.19rem] lg:border-x border-slate-3 pt-[5rem] w-full mx-auto",
+ ),
+ class_name="flex flex-col justify-center w-full h-auto relative max-w-[70.19rem] mx-auto",
+ )
diff --git a/pcweb/pages/pricing/calculator.py b/pcweb/pages/pricing/calculator.py
index cba0145ffc..5734a23b0a 100644
--- a/pcweb/pages/pricing/calculator.py
+++ b/pcweb/pages/pricing/calculator.py
@@ -235,7 +235,7 @@ def header() -> rx.Component:
"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",
),
- class_name="flex items-center mb-5 justify-between text-slate-11 flex-col pt-[5rem] 2xl:border-x border-slate-4 max-w-[64.125rem] mx-auto w-full",
+ class_name="flex items-center mb-5 justify-between text-slate-11 flex-col pt-[5rem] mx-auto w-full",
)
@@ -276,7 +276,7 @@ def calculator_section() -> rx.Component:
radial_circle(),
rx.box(
rx.flex(
- filtering_tags(),
+ # filtering_tags(),
align_items="center",
justify_content="center",
width="100%",
diff --git a/pcweb/pages/pricing/faq.py b/pcweb/pages/pricing/faq.py
index 12f57fa740..520fb5a1e9 100644
--- a/pcweb/pages/pricing/faq.py
+++ b/pcweb/pages/pricing/faq.py
@@ -35,7 +35,7 @@ def sales_button() -> rx.Component:
variant="secondary",
class_name="!text-slate-11 !font-semibold !text-sm",
),
- href="mailto:sales@reflex.dev", # TODO: Change to sales page when we have it
+ href="/sales",
is_external=True,
class_name="self-center relative",
)
diff --git a/pcweb/pages/pricing/plan_cards.py b/pcweb/pages/pricing/plan_cards.py
index 996d730928..6539f87916 100644
--- a/pcweb/pages/pricing/plan_cards.py
+++ b/pcweb/pages/pricing/plan_cards.py
@@ -1,5 +1,5 @@
import reflex as rx
-from pcweb.components.button import button
+from pcweb.components.new_button import button
from pcweb.constants import HOSTING_URL
@@ -25,6 +25,114 @@ def radial_circle(violet: bool = False) -> rx.Component:
)
+def glow() -> rx.Component:
+ return rx.html(
+ """""",
+ class_name="shrink-0 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-[-2] pointer-events-none",
+ )
+
+
+def grid() -> rx.Component:
+ return rx.html(
+ """
+""",
+ class_name="shrink-0 absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-[-2] pointer-events-none",
+ )
+
+
def card(
title: str, description: str, features: list[tuple[str, str]], button_text: str
) -> rx.Component:
@@ -36,9 +144,9 @@ def card(
rx.el.ul(
*[
rx.el.li(
- rx.icon(feature[0], class_name="!text-violet-9", size=16),
+ rx.icon(feature[0], class_name="!text-slate-9", size=16),
feature[1],
- class_name="text-sm font-medium text-slate-11 flex items-center gap-1.5",
+ class_name="text-sm font-medium text-slate-11 flex items-center gap-3",
)
for feature in features
],
@@ -49,17 +157,14 @@ def card(
button(
button_text,
variant="secondary",
- class_name="w-full !text-sm !font-semibold !text-slate-11",
- ),
- href=(
- HOSTING_URL
- if button_text != "Contact sales"
- else "/sales"
+ size="lg",
+ class_name="w-full",
),
+ href=(HOSTING_URL if button_text != "Contact sales" else "/sales"),
is_external=True,
underline="none",
),
- class_name="flex flex-col p-10 border border-slate-4 rounded-[1.125rem] shadow-small bg-slate-2 w-full min-w-[20.375rem] h-[30.5rem]",
+ class_name="flex flex-col p-8 border border-slate-4 rounded-[1.125rem] shadow-small bg-slate-2 w-full min-w-[20.375rem] h-[33.5rem]",
)
@@ -67,36 +172,41 @@ def popular_card(
title: str, description: str, features: list[tuple[str, str]], button_text: str
) -> rx.Component:
return rx.box(
- radial_circle(),
rx.box(
"Most popular",
- class_name="absolute top-[-0.75rem] left-8 rounded-md bg-[--violet-9] h-[1.5rem] z-[1] text-sm font-medium text-center px-2 flex items-center justify-center text-[#FCFCFD]",
- ),
- rx.el.h3(title, class_name="font-semibold text-slate-12 text-2xl mb-2"),
- rx.el.p(description, class_name="text-sm font-medium text-slate-9 mb-8"),
- rx.el.ul(
- *[
- rx.el.li(
- rx.icon(feature[0], class_name="!text-violet-9", size=16),
- feature[1],
- class_name="text-sm font-medium text-slate-11 flex items-center gap-1.5",
- )
- for feature in features
- ],
- class_name="flex flex-col gap-2",
+ class_name="absolute top-[-0.75rem] left-8 rounded-md bg-[--violet-9] h-[1.5rem] text-sm font-medium text-center px-2 flex items-center justify-center text-[#FCFCFD] z-[10]",
),
- rx.box(class_name="flex-1"),
- rx.link(
- button(
- button_text,
- variant="primary",
- class_name="w-full !text-sm !font-semibold",
+ rx.box(
+ glow(),
+ grid(),
+ rx.el.h3(title, class_name="font-semibold text-slate-12 text-2xl mb-2"),
+ rx.el.p(description, class_name="text-sm font-medium text-slate-9 mb-8"),
+ rx.el.ul(
+ *[
+ rx.el.li(
+ rx.icon(feature[0], class_name="!text-violet-9", size=16),
+ feature[1],
+ class_name="text-sm font-medium text-slate-11 flex items-center gap-3",
+ )
+ for feature in features
+ ],
+ class_name="flex flex-col gap-2",
),
- href=HOSTING_URL,
- is_external=True,
- underline="none",
+ rx.box(class_name="flex-1"),
+ rx.link(
+ button(
+ button_text,
+ variant="primary",
+ size="lg",
+ class_name="w-full !text-sm !font-semibold",
+ ),
+ href=f"{HOSTING_URL}/?redirect_url={HOSTING_URL}/billing/",
+ is_external=True,
+ underline="none",
+ ),
+ class_name="flex flex-col p-8 border border-[--violet-9] rounded-[1.125rem] w-full min-w-[20.375rem] h-[33.5rem] relative z-[1] backdrop-blur-[6px] bg-[rgba(249,_249,_251,_0.48)] dark:bg-[rgba(26,_27,_29,_0.48)] shadow-[0px_2px_5px_0px_rgba(28_32_36_0.03)] overflow-hidden",
),
- class_name="flex flex-col p-10 border border-slate-4 rounded-[1.125rem] shadow-small bg-slate-2 w-full min-w-[20.375rem] h-[30.5rem] relative z-[1]",
+ class_name="relative",
)
@@ -106,11 +216,11 @@ def plan_cards() -> rx.Component:
"Hobby",
"Everything you need to get started with Reflex.",
[
+ ("code", "Open Source Framework"),
("heart-handshake", "Community support"),
("user", "1 team member"),
- ("app-window", "1 deployed app"),
- ("list-minus", "1 day log retention"),
- ("map-pinned", "Single region"),
+ ("app-window", "1 Deployed app"),
+ ("file-code", "Starter Templates"),
],
"Start building for free",
),
@@ -118,12 +228,13 @@ def plan_cards() -> rx.Component:
"Pro",
"For professional projects $19/mo per member. Plus usage.",
[
- ("heart-handshake", "Community support"),
+ ("server", "Larger machine sizes"),
("users", "Up to 5 team members"),
- ("app-window", "Max 5 deployed apps"),
+ ("app-window", "Up to 5 Deployed apps"),
+ ("clock", "30 days log retention"),
("globe", "Multi-region"),
("brush", "Custom domains"),
- ("activity", "Basic analytics"),
+ ("wand", "AI Tools for App Building and Debugging"),
("circle-plus", "Everything in Hobby"),
],
"Start with Pro plan",
@@ -133,11 +244,13 @@ def plan_cards() -> rx.Component:
"For teams looking to scale their applications. Plus usage.",
[
("mail", "Email support"),
- ("users", "Up to 15 team members"),
+ ("users", "Up to 25 team members"),
("app-window", "Unlimited Apps"),
- ("list-minus", "90 days log retention"),
- ("activity", "Metrics and analytics"),
- ("lock-keyhole", "One-click Auth"),
+ ("signal", "Full Website Analytics"),
+ ("lock-keyhole", "One Click Auth"),
+ ("git-branch", "Dev, Stage & Prod Environments"),
+ ("database", "Database Editor UI and Migration Tool"),
+ ("test-tube", "Built-in Testing Framework"),
("circle-plus", "Everything in Pro"),
],
"Contact sales",
@@ -146,12 +259,15 @@ def plan_cards() -> rx.Component:
"Enterprise",
"Get our priority support and a plan tailored to your needs.",
[
- ("heart-handshake", "Priority Support + Custom Onboarding"),
+ ("headset", "Priority Engineering Support"),
+ ("user-round-plus", "White Glove Onboarding"),
("users", "Unlimited team members"),
- ("hard-drive", "On prem deployments"),
- ("list-minus", "Advanced app analytics"),
- ("cpu", "Customized machine size"),
- ("shield-check", "SOC 2 report"),
+ ("hard-drive", "On Premise Deployment"),
+ ("signal", "Full Analytics Dashboard of App Users"),
+ ("clock", "Unlimited log retention"),
+ ("activity", "Error Monitoring and Observability"),
+ ("git-pull-request", "Influence Reflex Roadmap"),
+ ("shield-check", "Custom SSO"),
("circle-plus", "Everything in Team"),
],
"Contact sales",
diff --git a/pcweb/pages/pricing/pricing.py b/pcweb/pages/pricing/pricing.py
index fe594d5ca3..7433113607 100644
--- a/pcweb/pages/pricing/pricing.py
+++ b/pcweb/pages/pricing/pricing.py
@@ -4,7 +4,7 @@
from pcweb.pages.index.views.footer_index import footer_index
from pcweb.pages.pricing.header import header
from pcweb.pages.pricing.plan_cards import plan_cards
-from pcweb.pages.pricing.table import comparison_table
+from pcweb.pages.pricing.table import comparison_table_hosting, comparison_table_oss
from pcweb.views.bottom_section.get_started import get_started
from pcweb.pages.pricing.faq import faq
from pcweb.pages.pricing.calculator import calculator_section
@@ -22,7 +22,8 @@ def pricing() -> rx.Component:
rx.box(
header(),
plan_cards(),
- comparison_table(),
+ comparison_table_hosting(),
+ comparison_table_oss(),
calculator_section(),
faq(),
class_name="flex flex-col relative justify-center items-center w-full",
diff --git a/pcweb/pages/pricing/table.py b/pcweb/pages/pricing/table.py
index f594398ecc..e4bc7913e5 100644
--- a/pcweb/pages/pricing/table.py
+++ b/pcweb/pages/pricing/table.py
@@ -33,46 +33,76 @@
"""
# Data configuration
-PRICE_SECTION = [
- ("Per Seat Price", "Free", "$19/mo", "Contact Sales", "Contact Sales"),
- ("Compute", "Free", "Usage Based", "Usage Based", "Custom"),
+USERS_SECTION = [
+ ("Per Seat Price", "Free", "$19/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),
+ ("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")]
-COMPUTE_SECTION = [
- ("Compute Limits", "1 CPU, .5GB", "5 CPU, 10GB", "32 CPU, 64GB", "No Limit"),
+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"),
+]
+
+DATABASE_SECTION = [
+ ("Connect your own SQL DB", True, True, True, True),
+ ("Database Editor UI", False, False, True, True),
+ ("Database Migration Tool", False, False, True, True),
+]
+
+HOSTING_TEXT_SECTION = [
+ ("Compute Limits", "1 CPU, .5GB", "5 CPU, 10GB", "Custom", "Custom"),
("Regions", "Single", "Multiple", "Multiple", "Multiple"),
- ("Team size", "1", "< 5", "< 15", "Unlimited"),
+ ("Custom Domains", "None", "1", "5", "Unlimited"),
+ ("Build logs", "7 day", "30 days", "90 days", "Custom"),
("Runtime logs", "1 day", "7 days", "30 days", "Custom"),
- ("Build logs", "1 days", "30 days", "90 days", "Custom"),
]
-ON_PREMISE_ROW = [("On Premises (Optional)", False, False, False, True)]
-
-FEATURE_SECTION = [
+HOSTING_BOOLEAN_SECTION = [
+ ("CLI Deployments", True, True, True, True),
+ ("Automatic CI / CD Deploy (Github)", False, False, True, True),
("Secrets", True, True, True, True),
- ("Custom domains", False, True, True, True),
- ("Metrics and analytics", False, True, True, True),
- ("Automatic CI/CD", False, True, True, True),
- ("Multi-region", False, True, True, True),
- ("One-click Auth", False, False, True, True),
- ("Cron jobs", False, False, True, True),
- ("SSO", False, False, False, True),
+ ("Secret Manager", False, False, True, True),
+ ("App Analytics", False, False, True, True),
+ ("Traces", False, False, True, True),
+ ("Custom Alerts", False, False, True, True),
+ ("Rollbacks", False, False, True, True),
+ ("Large File Support", False, False, True, True),
+ ("On Prem Hosting", False, False, False, True),
]
SECURITY_SECTION = [
- ("Web app firewall", True, True, True, True),
+ ("Web App Firewall", True, True, True, True),
("HTTP/SSL", True, True, True, True),
- ("DDos", True, True, True, True),
+ ("DDos Protection", True, True, True, True),
+ ("2 Factor Auth", True, True, True, True),
+ ("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),
]
+SUPPORT_TEXT_SECTION = [
+ ("Support", "Community", "Community", "Email Support", "Dedicated Support")
+]
-SUPPORT_SECTION = [
- ("Community support", True, True, True, True),
- ("Email (1 Business Day)", False, False, True, True),
- ("Support SLAs available", False, False, False, True),
- ("Custom onboarding", False, False, False, True),
- ("Migrate existing apps", False, False, False, True),
+SUPPORT_BOOLEAN_SECTION = [
+ ("White Glove Onboarding", False, False, False, True),
+ ("Support SLAs Available", False, False, False, True),
+ ("Migrate Existing Apps", False, False, False, True),
+ ("Priority Support with Reflex Engineering Team", False, False, False, True),
+ ("", "", "", "", ""),
]
PLAN_BUTTONS = [
@@ -95,20 +125,6 @@ def glow() -> rx.Component:
)
-def header() -> rx.Component:
- return rx.box(
- rx.el.h3(
- "Compare features across plans.",
- class_name="text-slate-12 text-3xl font-semibold text-center",
- ),
- rx.el.p(
- "Find a perfect fit",
- class_name="text-slate-9 text-3xl font-semibold text-center",
- ),
- class_name="flex items-center justify-between text-slate-11 flex-col py-[5rem] 2xl:border-x border-slate-4 max-w-[64.125rem] mx-auto w-full",
- )
-
-
def create_table_cell(content: str | rx.Component) -> rx.Component:
if content == "Usage Based":
return rx.table.cell(
@@ -127,7 +143,7 @@ def create_action_button(
variant=variant,
class_name=f"{STYLES['button_base']} {extra_styles}",
),
- href=HOSTING_URL if text != "Contact sales" else "mailto:sales@reflex.dev",
+ href=HOSTING_URL if text != "Contact sales" else "/sales",
is_external=True,
underline="none",
class_name="w-full flex justify-center items-center",
@@ -142,10 +158,10 @@ def create_table_row(cells: list) -> rx.Component:
)
-def create_table_row_header(cells: list) -> rx.Component:
+def create_table_row_header(cells: list, coming_soon: bool = False) -> rx.Component:
return rx.table.row(
*[
- rx.table.column_header_cell(cell, class_name=STYLES["header_cell"])
+ 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
],
class_name="w-full [&>*:not(:first-child)]:text-center bg-slate-2 border border-slate-3 rounded-2xl z-[6] !h-[3.625rem] relative",
@@ -156,7 +172,7 @@ def create_table_row_header(cells: list) -> rx.Component:
def create_table_body(*body_content) -> rx.Component:
return rx.table.body(
*body_content,
- class_name="w-full divide-y divide-slate-4 border border-slate-4 md:border-t-0 flex flex-col items-center justify-center border-x max-w-[64.125rem] mx-auto border-b-0",
+ class_name="w-full divide-y divide-slate-4 border border-slate-4 md:border-t-0 flex flex-col items-center justify-center border-x max-w-[64.19rem] mx-auto border-b-0",
)
@@ -174,7 +190,36 @@ def create_checkmark_row(feature: str, checks: tuple[bool, ...]) -> rx.Component
return create_table_row(cells)
-def table_body() -> rx.Component:
+
+def header_hosting() -> rx.Component:
+ return rx.box(
+ rx.el.h3(
+ "Secure and Scalable Hosting",
+ class_name="text-slate-12 text-3xl font-semibold text-center",
+ ),
+ rx.el.p(
+ "Compare features across plans.",
+ class_name="text-slate-9 text-2xl font-semibold text-center",
+ ),
+ class_name="flex items-center justify-between text-slate-11 flex-col py-[5rem] 2xl:border-x border-slate-4 max-w-[64.19rem] mx-auto w-full",
+ )
+
+
+def header_oss() -> rx.Component:
+ return rx.box(
+ rx.el.h3(
+ "Supercharged Features to Build Faster",
+ class_name="text-slate-12 text-3xl font-semibold text-center",
+ ),
+ rx.el.p(
+ "Premium Features to help you get the most out of Reflex",
+ class_name="text-slate-9 text-2xl font-semibold text-center",
+ ),
+ class_name="flex items-center justify-between text-slate-11 flex-col py-[5rem] 2xl:border-x border-slate-4 max-w-[64.19rem] mx-auto w-full",
+ )
+
+
+def table_body_hosting() -> rx.Component:
return rx.table.root(
rx.el.style(TABLE_STYLE),
rx.table.header(
@@ -183,48 +228,74 @@ def table_body() -> rx.Component:
class_name="relative",
),
create_table_body(
- *[create_table_row(row) for row in PRICE_SECTION],
+ *[create_table_row(row) for row in USERS_SECTION],
),
rx.table.header(
- create_table_row_header(["Compute", "", "", ""]),
+ create_table_row_header(["Hosting", "", "", ""]),
class_name="relative",
),
create_table_body(
+ *[create_table_row(row) for row in HOSTING_TEXT_SECTION],
*[
create_checkmark_row(feature, checks)
- for feature, *checks in ON_PREMISE_ROW
+ for feature, *checks in HOSTING_BOOLEAN_SECTION
],
- *[create_table_row(row) for row in COMPUTE_SECTION],
),
rx.table.header(
- create_table_row_header(["Features", "", "", "", ""]),
+ create_table_row_header(["Security", "", "", "", ""]),
class_name="relative",
),
create_table_body(
*[
create_checkmark_row(feature, checks)
- for feature, *checks in FEATURE_SECTION
+ for feature, *checks in SECURITY_SECTION
],
),
rx.table.header(
- create_table_row_header(["Security", "", "", "", ""]),
+ create_table_row_header(["Support", "", "", "", ""]),
class_name="relative",
),
create_table_body(
+ *[create_table_row(row) for row in SUPPORT_TEXT_SECTION],
*[
create_checkmark_row(feature, checks)
- for feature, *checks in SECURITY_SECTION
+ for feature, *checks in SUPPORT_BOOLEAN_SECTION
],
),
+ class_name="w-full overflow-x-auto max-w-[69.125rem] -mt-[2rem]",
+ )
+
+
+def table_body_oss() -> rx.Component:
+ return rx.table.root(
+ rx.el.style(TABLE_STYLE),
rx.table.header(
- create_table_row_header(["Support", "", "", "", ""]),
+ create_table_row_header(["Framework","Hobby", "Pro", "Team", "Enterprise"]),
class_name="relative",
),
create_table_body(
*[
create_checkmark_row(feature, checks)
- for feature, *checks in SUPPORT_SECTION
+ for feature, *checks in FRAMEWORK_SECTION
],
+ *[create_table_row(row) for row in THEME_SECTION],
+ ),
+ rx.table.header(
+ create_table_row_header(["Database", "", "", ""]),
+ class_name="relative",
+ ),
+ create_table_body(
+ *[
+ create_checkmark_row(feature, checks)
+ for feature, *checks in DATABASE_SECTION
+ ],
+ ),
+ 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],
),
create_table_body(
rx.table.row(
@@ -239,10 +310,17 @@ def table_body() -> rx.Component:
class_name="w-full overflow-x-auto max-w-[69.125rem] -mt-[2rem]",
)
-
-def comparison_table() -> rx.Component:
+def comparison_table_hosting() -> rx.Component:
return rx.box(
- header(),
- table_body(),
+ header_hosting(),
+ table_body_hosting(),
class_name="flex-col w-full max-w-[69.125rem] desktop-only",
)
+
+
+def comparison_table_oss() -> rx.Component:
+ return rx.box(
+ header_oss(),
+ table_body_oss(),
+ class_name="flex-col w-full max-w-[69.125rem] desktop-only",
+ )
\ No newline at end of file
diff --git a/pcweb/pages/sales.py b/pcweb/pages/sales.py
index b3a02e081e..8851344db5 100644
--- a/pcweb/pages/sales.py
+++ b/pcweb/pages/sales.py
@@ -72,7 +72,7 @@ def form() -> rx.Component:
rx.form(
rx.box(
rx.text(
- "Get an enterprise quote",
+ "Contact the Sales Team",
class_name="text-2xl text-slate-12 font-bold leading-6 scroll-m-[7rem]",
id="form-title",
),
@@ -151,7 +151,7 @@ def form() -> rx.Component:
FormState.is_loading,
button(
"Sending...",
- variant="muted",
+ variant="secondary",
type="submit",
class_name="opacity-80 !cursor-not-allowed pointer-events-none !w-min",
),