From bb7619ebea1defbce216b1f54df7c3e89e6273cc Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 24 Jan 2025 00:05:54 -0800 Subject: [PATCH] Add config `show_built_with_reflex` This config option is available for authenticated users on various plan tiers --- reflex/app.py | 19 +++++++++++++--- reflex/components/core/sticky.py | 2 ++ reflex/config.py | 3 +++ reflex/reflex.py | 16 ++++++++++++++ reflex/utils/export.py | 7 ++++++ reflex/utils/prerequisites.py | 37 +++++++++++++++++++++++++++++++- 6 files changed, 80 insertions(+), 4 deletions(-) diff --git a/reflex/app.py b/reflex/app.py index d432925ab..53aea477d 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -64,6 +64,7 @@ from reflex.components.core.client_side_routing import ( Default404Page, wait_for_client_redirect, ) +from reflex.components.core.sticky import sticky from reflex.components.core.upload import Upload, get_upload_dir from reflex.components.radix import themes from reflex.config import environment, get_config @@ -858,6 +859,15 @@ class App(MiddlewareMixin, LifespanMixin): continue self._pages[k] = self._add_error_boundary_to_component(component) + def _setup_sticky_badge(self): + """Add the sticky badge to the app.""" + for k, component in self._pages.items(): + # Would be nice to share single sticky_badge across all pages, but + # it bungles the StatefulComponent compile step. + sticky_badge = sticky() + sticky_badge._add_style_recursive({}) + self._pages[k] = Fragment.create(sticky_badge, component) + def _apply_decorated_pages(self): """Add @rx.page decorated pages to the app. @@ -946,10 +956,16 @@ class App(MiddlewareMixin, LifespanMixin): if not should_compile: return + # Get the env mode. + config = get_config() + self._validate_var_dependencies() self._setup_overlay_component() self._setup_error_boundary() + if config.show_built_with_reflex: + self._setup_sticky_badge() + # Create a progress bar. progress = Progress( *Progress.get_default_columns()[:-1], @@ -968,9 +984,6 @@ class App(MiddlewareMixin, LifespanMixin): + adhoc_steps_without_executor, ) - # Get the env mode. - config = get_config() - # Store the compile results. compile_results = [] diff --git a/reflex/components/core/sticky.py b/reflex/components/core/sticky.py index 6d4b2b7ca..cff158492 100644 --- a/reflex/components/core/sticky.py +++ b/reflex/components/core/sticky.py @@ -6,6 +6,7 @@ from reflex.components.core.cond import color_mode_cond from reflex.components.el.elements.media import Path, Rect, Svg from reflex.components.radix.themes.layout.box import Box from reflex.components.radix.themes.typography.text import Text +from reflex.event import redirect from reflex.style import Style @@ -85,6 +86,7 @@ class StickyBadge(Box): return super().create( StickyLogo.create(), StickyLabel.create(), + on_click=redirect("https://reflex.dev"), width="auto", padding="0.375rem", align="center", diff --git a/reflex/config.py b/reflex/config.py index 2daf8ef1d..a00c3c9f2 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -700,6 +700,9 @@ class Config(Base): # Path to file containing key-values pairs to override in the environment; Dotenv format. env_file: Optional[str] = None + # Whether to display the sticky "Built with Reflex" badge on all pages. + show_built_with_reflex: bool = True + def __init__(self, *args, **kwargs): """Initialize the config values. diff --git a/reflex/reflex.py b/reflex/reflex.py index 34a4a58a5..249b4848c 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -182,6 +182,14 @@ def _run( prerequisites.check_latest_package_version(constants.Reflex.MODULE_NAME) if frontend: + if not config.show_built_with_reflex: + # The sticky badge may be disabled at runtime for team/enterprise tiers. + prerequisites.check_config_option_in_tier( + option_name="show_built_with_reflex", + allowed_tiers=["team", "enterprise"], + fallback_value=True, + ) + # Get the app module. prerequisites.get_compiled_app() @@ -514,6 +522,14 @@ def deploy( check_version() + if not config.show_built_with_reflex: + # The sticky badge may be disabled on deploy for pro/team/enterprise tiers. + prerequisites.check_config_option_in_tier( + option_name="show_built_with_reflex", + allowed_tiers=["pro", "team", "enterprise"], + fallback_value=True, + ) + # Set the log level. console.set_log_level(loglevel) diff --git a/reflex/utils/export.py b/reflex/utils/export.py index edb4a6e1a..7fc38190e 100644 --- a/reflex/utils/export.py +++ b/reflex/utils/export.py @@ -55,6 +55,13 @@ def export( console.rule("[bold]Compiling production app and preparing for export.") if frontend: + if not config.show_built_with_reflex: + # The sticky badge may be disabled on export for team/enterprise tiers. + prerequisites.check_config_option_in_tier( + option_name="show_built_with_reflex", + allowed_tiers=["team", "enterprise"], + fallback_value=False, + ) # Ensure module can be imported and app.compile() is called. prerequisites.get_compiled_app(export=True) # Set up .web directory and install frontend dependencies. diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index 94d8f8fbd..d6baab185 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -23,7 +23,7 @@ import zipfile from datetime import datetime from pathlib import Path from types import ModuleType -from typing import Callable, List, NamedTuple, Optional +from typing import Any, Callable, List, NamedTuple, Optional import httpx import typer @@ -1967,3 +1967,38 @@ def is_generation_hash(template: str) -> bool: True if the template is composed of 32 or more hex characters. """ return re.match(r"^[0-9a-f]{32,}$", template) is not None + + +def check_config_option_in_tier( + option_name: str, + allowed_tiers: list[str], + fallback_value: Any, +): + """Check if a config option is allowed for the authenticated user's current tier. + + Args: + option_name: The name of the option to check. + allowed_tiers: The tiers that are allowed to use the option. + fallback_value: The fallback value if the option is not allowed. + """ + from reflex_cli.v2.utils import hosting + + config = get_config() + authenticated_token = hosting.authenticated_token() + the_remedy = [] + if not authenticated_token: + the_remedy.append( + "You are currently logged out. Run `reflex login` to access this option." + ) + current_tier = "anonymous" + else: + current_tier = authenticated_token[1].get("tier", "").lower() + the_remedy.append( + f"Your current subscription tier is `{current_tier}`. Please upgrade to {allowed_tiers} to access this option." + ) + if current_tier not in allowed_tiers: + console.warn( + f"Config option `{option_name}` is restricted. {'\n'.join(the_remedy)}" + ) + setattr(config, option_name, fallback_value) + config._set_persistent(**{option_name: fallback_value})