Browse Source

Support reflex app creation from templates from github (#2490)

Martin Xu 1 year ago
parent
commit
7d36610cae
28 changed files with 341 additions and 736 deletions
  1. 1 1
      .coveragerc
  2. 0 1
      integration/init-test/in_docker_test_script.sh
  3. 0 69
      reflex/.templates/apps/sidebar/README.md
  4. BIN
      reflex/.templates/apps/sidebar/assets/favicon.ico
  5. 0 10
      reflex/.templates/apps/sidebar/assets/github.svg
  6. 0 68
      reflex/.templates/apps/sidebar/assets/logo.svg
  7. 0 13
      reflex/.templates/apps/sidebar/assets/paneleft.svg
  8. 0 37
      reflex/.templates/apps/sidebar/assets/reflex_black.svg
  9. 0 8
      reflex/.templates/apps/sidebar/assets/reflex_white.svg
  10. 0 1
      reflex/.templates/apps/sidebar/code/__init__.py
  11. 0 0
      reflex/.templates/apps/sidebar/code/components/__init__.py
  12. 0 141
      reflex/.templates/apps/sidebar/code/components/sidebar.py
  13. 0 3
      reflex/.templates/apps/sidebar/code/pages/__init__.py
  14. 0 22
      reflex/.templates/apps/sidebar/code/pages/dashboard.py
  15. 0 18
      reflex/.templates/apps/sidebar/code/pages/index.py
  16. 0 76
      reflex/.templates/apps/sidebar/code/pages/settings.py
  17. 0 14
      reflex/.templates/apps/sidebar/code/sidebar.py
  18. 0 45
      reflex/.templates/apps/sidebar/code/styles.py
  19. 0 1
      reflex/.templates/apps/sidebar/code/templates/__init__.py
  20. 0 144
      reflex/.templates/apps/sidebar/code/templates/template.py
  21. 6 1
      reflex/.templates/web/utils/state.js
  22. 4 5
      reflex/constants/base.py
  23. 94 0
      reflex/constants/base.pyi
  24. 1 1
      reflex/custom_components/custom_components.py
  25. 5 30
      reflex/reflex.py
  26. 1 1
      reflex/testing.py
  27. 6 4
      reflex/utils/console.py
  28. 223 22
      reflex/utils/prerequisites.py

+ 1 - 1
.coveragerc

@@ -10,7 +10,7 @@ omit =
 [report]
 show_missing = true
 # TODO bump back to 79
-fail_under = 72
+fail_under = 70
 precision = 2
 
 # Regexes for lines to exclude from consideration

+ 0 - 1
integration/init-test/in_docker_test_script.sh

@@ -34,4 +34,3 @@ redis-server &
 
 echo "Running reflex init in test project dir"
 do_export blank
-do_export sidebar

+ 0 - 69
reflex/.templates/apps/sidebar/README.md

@@ -1,69 +0,0 @@
-# Welcome to Reflex!
-
-This is the base Reflex template - installed when you run `reflex init`.
-
-If you want to use a different template, pass the `--template` flag to `reflex init`.
-For example, if you want a more basic starting point, you can run:
-
-```bash
-reflex init --template blank
-```
-
-## About this Template
-
-This template has the following directory structure:
-
-```bash
-├── README.md
-├── assets
-├── rxconfig.py
-└── {your_app}
-    ├── __init__.py
-    ├── components
-    │   ├── __init__.py
-    │   └── sidebar.py
-    ├── pages
-    │   ├── __init__.py
-    │   ├── dashboard.py
-    │   ├── index.py
-    │   └── settings.py
-    ├── styles.py
-    ├── templates
-    │   ├── __init__.py
-    │   └── template.py
-    └── {your_app}.py
-```
-
-See the [Project Structure docs](https://reflex.dev/docs/getting-started/project-structure/) for more information on general Reflex project structure.
-
-### Adding Pages
-
-In this template, the pages in your app are defined in `{your_app}/pages/`.
-Each page is a function that returns a Reflex component.
-For example, to edit this page you can modify `{your_app}/pages/index.py`.
-See the [pages docs](https://reflex.dev/docs/pages/routes/) for more information on pages.
-
-In this template, instead of using `rx.add_page` or the `@rx.page` decorator,
-we use the `@template` decorator from `{your_app}/templates/template.py`.
-
-To add a new page:
-
-1. Add a new file in `{your_app}/pages/`. We recommend using one file per page, but you can also group pages in a single file.
-2. Add a new function with the `@template` decorator, which takes the same arguments as `@rx.page`.
-3. Import the page in your `{your_app}/pages/__init__.py` file and it will automatically be added to the app.
-
-
-### Adding Components
-
-In order to keep your code organized, we recommend putting components that are
-used across multiple pages in the `{your_app}/components/` directory.
-
-In this template, we have a sidebar component in `{your_app}/components/sidebar.py`.
-
-### Adding State
-
-As your app grows, we recommend using [substates](https://reflex.dev/docs/substates/overview/)
-to organize your state.
-
-You can either define substates in their own files, or if the state is
-specific to a page, you can define it in the page file itself.

BIN
reflex/.templates/apps/sidebar/assets/favicon.ico


+ 0 - 10
reflex/.templates/apps/sidebar/assets/github.svg

@@ -1,10 +0,0 @@
-<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<g id="Github" clip-path="url(#clip0_469_1929)">
-<path id="Vector" d="M8.0004 0.587524C3.80139 0.587524 0.400391 3.98851 0.400391 8.1875C0.400391 11.5505 2.57589 14.391 5.59689 15.398C5.97689 15.4645 6.11939 15.2365 6.11939 15.037C6.11939 14.8565 6.10989 14.258 6.10989 13.6215C4.20039 13.973 3.70639 13.156 3.55439 12.7285C3.46889 12.51 3.09839 11.8355 2.77539 11.655C2.50939 11.5125 2.12939 11.161 2.76589 11.1515C3.36439 11.142 3.79189 11.7025 3.93439 11.9305C4.61839 13.08 5.71089 12.757 6.14789 12.5575C6.21439 12.0635 6.41388 11.731 6.6324 11.541C4.94139 11.351 3.17439 10.6955 3.17439 7.7885C3.17439 6.962 3.46889 6.27801 3.95339 5.74601C3.87739 5.55601 3.61139 4.77701 4.02939 3.73201C4.02939 3.73201 4.66589 3.53251 6.11939 4.51101C6.7274 4.34001 7.3734 4.25451 8.0194 4.25451C8.6654 4.25451 9.3114 4.34001 9.9194 4.51101C11.3729 3.52301 12.0094 3.73201 12.0094 3.73201C12.4274 4.77701 12.1614 5.55601 12.0854 5.74601C12.5699 6.27801 12.8644 6.9525 12.8644 7.7885C12.8644 10.705 11.0879 11.351 9.3969 11.541C9.6724 11.7785 9.9099 12.2345 9.9099 12.947C9.9099 13.9635 9.9004 14.7805 9.9004 15.037C9.9004 15.2365 10.0429 15.474 10.4229 15.398C13.5165 14.3536 15.5996 11.4527 15.6004 8.1875C15.6004 3.98851 12.1994 0.587524 8.0004 0.587524Z" fill="#494369"/>
-</g>
-<defs>
-<clipPath id="clip0_469_1929">
-<rect width="16" height="16" fill="white"/>
-</clipPath>
-</defs>
-</svg>

+ 0 - 68
reflex/.templates/apps/sidebar/assets/logo.svg

@@ -1,68 +0,0 @@
-<svg width="80" height="78" viewBox="0 0 80 78" fill="none" xmlns="http://www.w3.org/2000/svg">
-<g filter="url(#filter0_ddddi_449_2821)">
-<path d="M13 11C13 6.58172 16.5817 3 21 3H59C63.4183 3 67 6.58172 67 11V49C67 52.3137 64.3137 55 61 55H19C15.6863 55 13 52.3137 13 49V11Z" fill="url(#paint0_radial_449_2821)"/>
-<path d="M13 11C13 6.58172 16.5817 3 21 3H59C63.4183 3 67 6.58172 67 11V49C67 52.3137 64.3137 55 61 55H19C15.6863 55 13 52.3137 13 49V11Z" fill="url(#paint1_radial_449_2821)"/>
-<g filter="url(#filter1_i_449_2821)">
-<path d="M31 37.5C30.4477 37.5 30 37.0523 30 36.5V13.5001C30 12.9478 30.4477 12.5001 31 12.5001H49C49.5523 12.5001 50 12.9478 50 13.5001V21.5001C50 22.0524 49.5523 22.5001 49 22.5001H45V18.5001C45 17.9478 44.5523 17.5001 44 17.5001H36C35.4477 17.5001 35 17.9478 35 18.5001V21.5001C35 22.0524 35.4477 22.5001 36 22.5001H45V27.5001H36C35.4477 27.5001 35 27.9478 35 28.5001V36.5C35 37.0523 34.5523 37.5 34 37.5H31ZM46 37.5C45.4477 37.5 45 37.0523 45 36.5V27.5001H49C49.5523 27.5001 50 27.9478 50 28.5001V36.5C50 37.0523 49.5523 37.5 49 37.5H46Z" fill="url(#paint2_radial_449_2821)"/>
-</g>
-<path d="M13 11C13 6.58172 16.5817 3 21 3H59C63.4183 3 67 6.58172 67 11V49C67 52.3137 64.3137 55 61 55H19C15.6863 55 13 52.3137 13 49V11Z" stroke="#20117E" stroke-opacity="0.04"/>
-</g>
-<defs>
-<filter id="filter0_ddddi_449_2821" x="0.5" y="0.5" width="79" height="77" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
-<feFlood flood-opacity="0" result="BackgroundImageFix"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feMorphology radius="4" operator="erode" in="SourceAlpha" result="effect1_dropShadow_449_2821"/>
-<feOffset dy="10"/>
-<feGaussianBlur stdDeviation="8"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0.0784314 0 0 0 0 0.0705882 0 0 0 0 0.231373 0 0 0 0.06 0"/>
-<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_449_2821"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feMorphology radius="6" operator="erode" in="SourceAlpha" result="effect2_dropShadow_449_2821"/>
-<feOffset dy="12"/>
-<feGaussianBlur stdDeviation="3"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0.0784314 0 0 0 0 0.0705882 0 0 0 0 0.231373 0 0 0 0.1 0"/>
-<feBlend mode="normal" in2="effect1_dropShadow_449_2821" result="effect2_dropShadow_449_2821"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feMorphology radius="4" operator="erode" in="SourceAlpha" result="effect3_dropShadow_449_2821"/>
-<feOffset dy="10"/>
-<feGaussianBlur stdDeviation="3"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0.12549 0 0 0 0 0.0666667 0 0 0 0 0.494118 0 0 0 0.16 0"/>
-<feBlend mode="normal" in2="effect2_dropShadow_449_2821" result="effect3_dropShadow_449_2821"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feMorphology radius="1" operator="dilate" in="SourceAlpha" result="effect4_dropShadow_449_2821"/>
-<feOffset dy="2"/>
-<feGaussianBlur stdDeviation="1"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0.12549 0 0 0 0 0.0666667 0 0 0 0 0.494118 0 0 0 0.05 0"/>
-<feBlend mode="normal" in2="effect3_dropShadow_449_2821" result="effect4_dropShadow_449_2821"/>
-<feBlend mode="normal" in="SourceGraphic" in2="effect4_dropShadow_449_2821" result="shape"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feOffset dy="-8"/>
-<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0.678431 0 0 0 0 0.607843 0 0 0 0 0.972549 0 0 0 0.2 0"/>
-<feBlend mode="normal" in2="shape" result="effect5_innerShadow_449_2821"/>
-</filter>
-<filter id="filter1_i_449_2821" x="30" y="12.5001" width="20" height="26.9999" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
-<feFlood flood-opacity="0" result="BackgroundImageFix"/>
-<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
-<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
-<feOffset dy="2"/>
-<feGaussianBlur stdDeviation="1.5"/>
-<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
-<feColorMatrix type="matrix" values="0 0 0 0 0.12549 0 0 0 0 0.0666667 0 0 0 0 0.494118 0 0 0 0.32 0"/>
-<feBlend mode="normal" in2="shape" result="effect1_innerShadow_449_2821"/>
-</filter>
-<radialGradient id="paint0_radial_449_2821" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(40 3) rotate(90) scale(52 54)">
-<stop stop-color="white" stop-opacity="0.9"/>
-<stop offset="1" stop-color="#4E3DB9" stop-opacity="0.24"/>
-</radialGradient>
-<radialGradient id="paint1_radial_449_2821" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(40 3) rotate(90) scale(52 54)">
-<stop stop-color="white"/>
-<stop offset="1" stop-color="#F7F7F7"/>
-</radialGradient>
-<radialGradient id="paint2_radial_449_2821" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(40 12.5001) rotate(90) scale(24.9999 20)">
-<stop stop-color="#F5F3FF"/>
-<stop stop-color="white"/>
-<stop offset="1" stop-color="#E1DDF4"/>
-</radialGradient>
-</defs>
-</svg>

+ 0 - 13
reflex/.templates/apps/sidebar/assets/paneleft.svg

@@ -1,13 +0,0 @@
-<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
-<g id="PaneLeft" clip-path="url(#clip0_469_1942)">
-<g id="Vector">
-<path fill-rule="evenodd" clip-rule="evenodd" d="M7.80217 0.525009C7.34654 0.525009 6.97717 0.894373 6.97717 1.35001V10.65C6.97717 11.1056 7.34654 11.475 7.80217 11.475H10.6522C11.1078 11.475 11.4772 11.1056 11.4772 10.65V1.35001C11.4772 0.894373 11.1078 0.525009 10.6522 0.525009H7.80217ZM8.02717 10.425V1.57501H10.4272V10.425H8.02717Z" fill="#494369"/>
-<path d="M3.78215 8.14502L2.16213 6.525H5.92717V5.475H2.16213L3.78215 3.85498L3.03969 3.11252L0.523438 5.62877V6.37123L3.03969 8.88748L3.78215 8.14502Z" fill="#494369"/>
-</g>
-</g>
-<defs>
-<clipPath id="clip0_469_1942">
-<rect width="12" height="12" fill="white"/>
-</clipPath>
-</defs>
-</svg>

+ 0 - 37
reflex/.templates/apps/sidebar/assets/reflex_black.svg

@@ -1,37 +0,0 @@
-<svg width="67" height="14" viewBox="0 0 67 14" fill="none" xmlns="http://www.w3.org/2000/svg">
-<rect width="67" height="14" fill="#1E1E1E"/>
-<g id="Nav Template &#62; Initial" clip-path="url(#clip0_0_1)">
-<rect width="1440" height="1024" transform="translate(-16 -17)" fill="white"/>
-<g id="Sidebar">
-<g clip-path="url(#clip1_0_1)">
-<path d="M-16 -17H264V1007H-16V-17Z" fill="white"/>
-<g id="Header">
-<path d="M-16 -17H264V31H-16V-17Z" fill="white"/>
-<g id="Button">
-<rect x="-4" y="-3" width="74.316" height="20" rx="6" fill="white"/>
-<g id="Logo">
-<g id="Reflex">
-<path d="M0 13.6316V0.368408H10.6106V5.67369H7.95792V3.02105H2.65264V5.67369H7.95792V8.32633H2.65264V13.6316H0ZM7.95792 13.6316V8.32633H10.6106V13.6316H7.95792Z" fill="#110F1F"/>
-<path d="M13.2632 13.6316V0.368408H21.2211V3.02105H15.9158V5.67369H21.2211V8.32633H15.9158V10.979H21.2211V13.6316H13.2632Z" fill="#110F1F"/>
-<path d="M23.8738 13.6316V0.368408H31.8317V3.02105H26.5264V5.67369H31.8317V8.32633H26.5264V13.6316H23.8738Z" fill="#110F1F"/>
-<path d="M34.4843 13.6316V0.368408H37.137V10.979H42.4422V13.6316H34.4843Z" fill="#110F1F"/>
-<path d="M45.0949 13.6316V0.368408H53.0528V3.02105H47.7475V5.67369H53.0528V8.32633H47.7475V10.979H53.0528V13.6316H45.0949Z" fill="#110F1F"/>
-<path d="M55.7054 5.67369V0.368408H58.3581V5.67369H55.7054ZM63.6634 5.67369V0.368408H66.316V5.67369H63.6634ZM58.3581 8.32633V5.67369H63.6634V8.32633H58.3581ZM55.7054 13.6316V8.32633H58.3581V13.6316H55.7054ZM63.6634 13.6316V8.32633H66.316V13.6316H63.6634Z" fill="#110F1F"/>
-</g>
-</g>
-</g>
-<path d="M264 30.5H-16V31.5H264V30.5Z" fill="#F4F3F6"/>
-</g>
-</g>
-<path d="M263.5 -17V1007H264.5V-17H263.5Z" fill="#F4F3F6"/>
-</g>
-</g>
-<defs>
-<clipPath id="clip0_0_1">
-<rect width="1440" height="1024" fill="white" transform="translate(-16 -17)"/>
-</clipPath>
-<clipPath id="clip1_0_1">
-<path d="M-16 -17H264V1007H-16V-17Z" fill="white"/>
-</clipPath>
-</defs>
-</svg>

+ 0 - 8
reflex/.templates/apps/sidebar/assets/reflex_white.svg

@@ -1,8 +0,0 @@
-<svg width="56" height="12" viewBox="0 0 56 12" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M0 11.5999V0.399902H8.96V4.8799H6.72V2.6399H2.24V4.8799H6.72V7.1199H2.24V11.5999H0ZM6.72 11.5999V7.1199H8.96V11.5999H6.72Z" fill="white"/>
-<path d="M11.2 11.5999V0.399902H17.92V2.6399H13.44V4.8799H17.92V7.1199H13.44V9.3599H17.92V11.5999H11.2Z" fill="white"/>
-<path d="M20.16 11.5999V0.399902H26.88V2.6399H22.4V4.8799H26.88V7.1199H22.4V11.5999H20.16Z" fill="white"/>
-<path d="M29.12 11.5999V0.399902H31.36V9.3599H35.84V11.5999H29.12Z" fill="white"/>
-<path d="M38.08 11.5999V0.399902H44.8V2.6399H40.32V4.8799H44.8V7.1199H40.32V9.3599H44.8V11.5999H38.08Z" fill="white"/>
-<path d="M47.04 4.8799V0.399902H49.28V4.8799H47.04ZM53.76 4.8799V0.399902H56V4.8799H53.76ZM49.28 7.1199V4.8799H53.76V7.1199H49.28ZM47.04 11.5999V7.1199H49.28V11.5999H47.04ZM53.76 11.5999V7.1199H56V11.5999H53.76Z" fill="white"/>
-</svg>

+ 0 - 1
reflex/.templates/apps/sidebar/code/__init__.py

@@ -1 +0,0 @@
-"""Base template for Reflex."""

+ 0 - 0
reflex/.templates/apps/sidebar/code/components/__init__.py


+ 0 - 141
reflex/.templates/apps/sidebar/code/components/sidebar.py

@@ -1,141 +0,0 @@
-"""Sidebar component for the app."""
-
-from code import styles
-
-import reflex as rx
-
-
-def sidebar_header() -> rx.Component:
-    """Sidebar header.
-
-    Returns:
-        The sidebar header component.
-    """
-    return rx.hstack(
-        # The logo.
-        rx.color_mode_cond(
-            rx.image(src="/reflex_black.svg", height="2em"),
-            rx.image(src="/reflex_white.svg", height="2em"),
-        ),
-        rx.spacer(),
-        rx.link(
-            rx.button(
-                rx.icon("github"),
-                color_scheme="gray",
-                variant="soft",
-            ),
-            href="https://github.com/reflex-dev/reflex",
-        ),
-        align="center",
-        width="100%",
-        border_bottom=styles.border,
-        padding_x="1em",
-        padding_y="2em",
-    )
-
-
-def sidebar_footer() -> rx.Component:
-    """Sidebar footer.
-
-    Returns:
-        The sidebar footer component.
-    """
-    return rx.hstack(
-        rx.spacer(),
-        rx.link(
-            rx.text("Docs"),
-            href="https://reflex.dev/docs/getting-started/introduction/",
-            color_scheme="gray",
-        ),
-        rx.link(
-            rx.text("Blog"),
-            href="https://reflex.dev/blog/",
-            color_scheme="gray",
-        ),
-        width="100%",
-        border_top=styles.border,
-        padding="1em",
-    )
-
-
-def sidebar_item(text: str, url: str) -> rx.Component:
-    """Sidebar item.
-
-    Args:
-        text: The text of the item.
-        url: The URL of the item.
-
-    Returns:
-        rx.Component: The sidebar item component.
-    """
-    # Whether the item is active.
-    active = (rx.State.router.page.path == f"/{text.lower()}") | (
-        (rx.State.router.page.path == "/") & text == "Home"
-    )
-
-    return rx.link(
-        rx.hstack(
-            rx.text(
-                text,
-            ),
-            bg=rx.cond(
-                active,
-                rx.color("accent", 2),
-                "transparent",
-            ),
-            border=rx.cond(
-                active,
-                f"1px solid {rx.color('accent', 6)}",
-                f"1px solid {rx.color('gray', 6)}",
-            ),
-            color=rx.cond(
-                active,
-                styles.accent_text_color,
-                styles.text_color,
-            ),
-            align="center",
-            border_radius=styles.border_radius,
-            width="100%",
-            padding="1em",
-        ),
-        href=url,
-        width="100%",
-    )
-
-
-def sidebar() -> rx.Component:
-    """The sidebar.
-
-    Returns:
-        The sidebar component.
-    """
-    # Get all the decorated pages and add them to the sidebar.
-    from reflex.page import get_decorated_pages
-
-    return rx.box(
-        rx.vstack(
-            sidebar_header(),
-            rx.vstack(
-                *[
-                    sidebar_item(
-                        text=page.get("title", page["route"].strip("/").capitalize()),
-                        url=page["route"],
-                    )
-                    for page in get_decorated_pages()
-                ],
-                width="100%",
-                overflow_y="auto",
-                align_items="flex-start",
-                padding="1em",
-            ),
-            rx.spacer(),
-            sidebar_footer(),
-            height="100dvh",
-        ),
-        display=["none", "none", "block"],
-        min_width=styles.sidebar_width,
-        height="100%",
-        position="sticky",
-        top="0px",
-        border_right=styles.border,
-    )

+ 0 - 3
reflex/.templates/apps/sidebar/code/pages/__init__.py

@@ -1,3 +0,0 @@
-from .dashboard import dashboard
-from .index import index
-from .settings import settings

+ 0 - 22
reflex/.templates/apps/sidebar/code/pages/dashboard.py

@@ -1,22 +0,0 @@
-"""The dashboard page."""
-
-from code.templates import template
-
-import reflex as rx
-
-
-@template(route="/dashboard", title="Dashboard")
-def dashboard() -> rx.Component:
-    """The dashboard page.
-
-    Returns:
-        The UI for the dashboard page.
-    """
-    return rx.vstack(
-        rx.heading("Dashboard", size="8"),
-        rx.text("Welcome to Reflex!"),
-        rx.text(
-            "You can edit this page in ",
-            rx.code("{your_app}/pages/dashboard.py"),
-        ),
-    )

+ 0 - 18
reflex/.templates/apps/sidebar/code/pages/index.py

@@ -1,18 +0,0 @@
-"""The home page of the app."""
-
-from code import styles
-from code.templates import template
-
-import reflex as rx
-
-
-@template(route="/", title="Home")
-def index() -> rx.Component:
-    """The home page.
-
-    Returns:
-        The UI for the home page.
-    """
-    with open("README.md", encoding="utf-8") as readme:
-        content = readme.read()
-    return rx.markdown(content, component_map=styles.markdown_style)

+ 0 - 76
reflex/.templates/apps/sidebar/code/pages/settings.py

@@ -1,76 +0,0 @@
-"""The settings page."""
-
-from code.templates import ThemeState, template
-
-import reflex as rx
-
-
-@template(route="/settings", title="Settings")
-def settings() -> rx.Component:
-    """The settings page.
-
-    Returns:
-        The UI for the settings page.
-    """
-    return rx.vstack(
-        rx.heading("Settings", size="8"),
-        rx.hstack(
-            rx.text("Dark mode: "),
-            rx.color_mode.switch(),
-        ),
-        rx.hstack(
-            rx.text("Primary color: "),
-            rx.select(
-                [
-                    "tomato",
-                    "red",
-                    "ruby",
-                    "crimson",
-                    "pink",
-                    "plum",
-                    "purple",
-                    "violet",
-                    "iris",
-                    "indigo",
-                    "blue",
-                    "cyan",
-                    "teal",
-                    "jade",
-                    "green",
-                    "grass",
-                    "brown",
-                    "orange",
-                    "sky",
-                    "mint",
-                    "lime",
-                    "yellow",
-                    "amber",
-                    "gold",
-                    "bronze",
-                    "gray",
-                ],
-                value=ThemeState.accent_color,
-                on_change=ThemeState.set_accent_color,
-            ),
-        ),
-        rx.hstack(
-            rx.text("Secondary color: "),
-            rx.select(
-                [
-                    "gray",
-                    "mauve",
-                    "slate",
-                    "sage",
-                    "olive",
-                    "sand",
-                ],
-                value=ThemeState.gray_color,
-                on_change=ThemeState.set_gray_color,
-            ),
-        ),
-        rx.text(
-            "You can edit this page in ",
-            rx.code("{your_app}/pages/settings.py"),
-            size="1",
-        ),
-    )

+ 0 - 14
reflex/.templates/apps/sidebar/code/sidebar.py

@@ -1,14 +0,0 @@
-"""Welcome to Reflex!."""
-
-# Import all the pages.
-from code.pages import *
-
-import reflex as rx
-
-
-class State(rx.State):
-    """Define empty state to allow access to rx.State.router."""
-
-
-# Create the app.
-app = rx.App()

+ 0 - 45
reflex/.templates/apps/sidebar/code/styles.py

@@ -1,45 +0,0 @@
-"""Styles for the app."""
-
-import reflex as rx
-
-border_radius = "0.375rem"
-border = f"1px solid {rx.color('gray', 6)}"
-text_color = rx.color("gray", 11)
-accent_text_color = rx.color("accent", 10)
-accent_color = rx.color("accent", 1)
-hover_accent_color = {"_hover": {"color": accent_text_color}}
-hover_accent_bg = {"_hover": {"background_color": accent_color}}
-content_width_vw = "90vw"
-sidebar_width = "20em"
-
-template_page_style = {"padding_top": "5em", "padding_x": ["auto", "2em"], "flex": "1"}
-
-template_content_style = {
-    "align_items": "flex-start",
-    "border_radius": border_radius,
-    "padding": "1em",
-    "margin_bottom": "2em",
-}
-
-link_style = {
-    "color": accent_text_color,
-    "text_decoration": "none",
-    **hover_accent_color,
-}
-
-overlapping_button_style = {
-    "background_color": "white",
-    "border_radius": border_radius,
-}
-
-markdown_style = {
-    "code": lambda text: rx.code(text, color_scheme="gray"),
-    "codeblock": lambda text, **props: rx.code_block(text, **props, margin_y="1em"),
-    "a": lambda text, **props: rx.link(
-        text,
-        **props,
-        font_weight="bold",
-        text_decoration="underline",
-        text_decoration_color=accent_text_color,
-    ),
-}

+ 0 - 1
reflex/.templates/apps/sidebar/code/templates/__init__.py

@@ -1 +0,0 @@
-from .template import ThemeState, template

+ 0 - 144
reflex/.templates/apps/sidebar/code/templates/template.py

@@ -1,144 +0,0 @@
-"""Common templates used between pages in the app."""
-
-from __future__ import annotations
-
-from code import styles
-from code.components.sidebar import sidebar
-from typing import Callable
-
-import reflex as rx
-
-# Meta tags for the app.
-default_meta = [
-    {
-        "name": "viewport",
-        "content": "width=device-width, shrink-to-fit=no, initial-scale=1",
-    },
-]
-
-
-def menu_item_link(text, href):
-    return rx.menu.item(
-        rx.link(
-            text,
-            href=href,
-            width="100%",
-            color="inherit",
-        ),
-        _hover={
-            "color": styles.accent_color,
-            "background_color": styles.accent_text_color,
-        },
-    )
-
-
-def menu_button() -> rx.Component:
-    """The menu button on the top right of the page.
-
-    Returns:
-        The menu button component.
-    """
-    from reflex.page import get_decorated_pages
-
-    return rx.box(
-        rx.menu.root(
-            rx.menu.trigger(
-                rx.button(
-                    rx.icon("menu"),
-                    variant="soft",
-                )
-            ),
-            rx.menu.content(
-                *[
-                    menu_item_link(page["title"], page["route"])
-                    for page in get_decorated_pages()
-                ],
-                rx.menu.separator(),
-                menu_item_link("About", "https://github.com/reflex-dev"),
-                menu_item_link("Contact", "mailto:founders@=reflex.dev"),
-            ),
-        ),
-        position="fixed",
-        right="2em",
-        top="2em",
-        z_index="500",
-    )
-
-
-class ThemeState(rx.State):
-    """The state for the theme of the app."""
-
-    accent_color: str = "crimson"
-
-    gray_color: str = "gray"
-
-
-def template(
-    route: str | None = None,
-    title: str | None = None,
-    description: str | None = None,
-    meta: str | None = None,
-    script_tags: list[rx.Component] | None = None,
-    on_load: rx.event.EventHandler | list[rx.event.EventHandler] | None = None,
-) -> Callable[[Callable[[], rx.Component]], rx.Component]:
-    """The template for each page of the app.
-
-    Args:
-        route: The route to reach the page.
-        title: The title of the page.
-        description: The description of the page.
-        meta: Additionnal meta to add to the page.
-        on_load: The event handler(s) called when the page load.
-        script_tags: Scripts to attach to the page.
-
-    Returns:
-        The template with the page content.
-    """
-
-    def decorator(page_content: Callable[[], rx.Component]) -> rx.Component:
-        """The template for each page of the app.
-
-        Args:
-            page_content: The content of the page.
-
-        Returns:
-            The template with the page content.
-        """
-        # Get the meta tags for the page.
-        all_meta = [*default_meta, *(meta or [])]
-
-        def templated_page():
-            return rx.hstack(
-                sidebar(),
-                rx.box(
-                    rx.box(
-                        page_content(),
-                        **styles.template_content_style,
-                    ),
-                    **styles.template_page_style,
-                ),
-                menu_button(),
-                align="start",
-                background=f"radial-gradient(circle at top right, {rx.color('accent', 2)}, {rx.color('mauve', 1)});",
-                position="relative",
-            )
-
-        @rx.page(
-            route=route,
-            title=title,
-            description=description,
-            meta=all_meta,
-            script_tags=script_tags,
-            on_load=on_load,
-        )
-        def theme_wrap():
-            return rx.theme(
-                templated_page(),
-                has_background=True,
-                accent_color=ThemeState.accent_color,
-                gray_color=ThemeState.gray_color,
-            )
-
-        return theme_wrap
-
-    return decorator

+ 6 - 1
reflex/.templates/web/utils/state.js

@@ -646,7 +646,12 @@ export const useEventLoop = (
 
   // Route after the initial page hydration.
   useEffect(() => {
-    const change_start = () => dispatch["state"]({is_hydrated: false})
+    const change_start = () => {
+      const main_state_dispatch = dispatch["state"]
+      if (main_state_dispatch !== undefined) {
+        main_state_dispatch({is_hydrated: false})
+      }
+    }
     const change_complete = () => addEvents(onLoadInternalEvent());
     router.events.on("routeChangeStart", change_start);
     router.events.on("routeChangeComplete", change_complete);

+ 4 - 5
reflex/constants/base.py

@@ -88,12 +88,11 @@ class ReflexHostingCLI(SimpleNamespace):
 class Templates(SimpleNamespace):
     """Constants related to Templates."""
 
-    # Dynamically get the enum values from the .templates folder
-    template_dir = os.path.join(Reflex.ROOT_DIR, Reflex.MODULE_NAME, ".templates/apps")
-    template_dirs = next(os.walk(template_dir))[1]
+    # The route on Reflex backend to query which templates are available and their URLs.
+    APP_TEMPLATES_ROUTE = "/app-templates"
 
-    # Create an enum value for each directory in the .templates folder
-    Kind = Enum("Kind", {template.upper(): template for template in template_dirs})
+    # The default template
+    DEFAULT = "blank"
 
     class Dirs(SimpleNamespace):
         """Folders used by the template system of Reflex."""

+ 94 - 0
reflex/constants/base.pyi

@@ -0,0 +1,94 @@
+"""Stub file for reflex/constants/base.py"""
+# ------------------- DO NOT EDIT ----------------------
+# This file was generated by `reflex/utils/pyi_generator.py`!
+# ------------------------------------------------------
+
+from typing import Any, Dict, Literal, Optional, Union, overload
+from reflex.vars import Var, BaseVar, ComputedVar
+from reflex.event import EventChain, EventHandler, EventSpec
+from reflex.style import Style
+import os
+import platform
+from enum import Enum
+from importlib import metadata
+from types import SimpleNamespace
+from platformdirs import PlatformDirs
+
+IS_WINDOWS = platform.system() == "Windows"
+
+class Dirs(SimpleNamespace):
+    WEB = ".web"
+    APP_ASSETS = "assets"
+    UTILS = "utils"
+    STATIC = "_static"
+    STATE_PATH = "/".join([UTILS, "state"])
+    COMPONENTS_PATH = "/".join([UTILS, "components"])
+    CONTEXTS_PATH = "/".join([UTILS, "context"])
+    WEB_PAGES = os.path.join(WEB, "pages")
+    WEB_STATIC = os.path.join(WEB, STATIC)
+    WEB_UTILS = os.path.join(WEB, UTILS)
+    WEB_ASSETS = os.path.join(WEB, "public")
+    ENV_JSON = os.path.join(WEB, "env.json")
+    REFLEX_JSON = os.path.join(WEB, "reflex.json")
+    POSTCSS_JS = os.path.join(WEB, "postcss.config.js")
+
+class Reflex(SimpleNamespace):
+    MODULE_NAME = "reflex"
+    VERSION = metadata.version(MODULE_NAME)
+    JSON = os.path.join(Dirs.WEB, "reflex.json")
+    _dir = os.environ.get("REFLEX_DIR", "")
+    DIR = _dir or PlatformDirs(MODULE_NAME, False).user_data_dir
+    ROOT_DIR = os.path.dirname(
+        os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+    )
+
+class ReflexHostingCLI(SimpleNamespace):
+    MODULE_NAME = "reflex-hosting-cli"
+
+class Templates(SimpleNamespace):
+    APP_TEMPLATES_ROUTE = "/app-templates"
+    DEFAULT = "blank"
+
+    class Dirs(SimpleNamespace):
+        BASE = os.path.join(Reflex.ROOT_DIR, Reflex.MODULE_NAME, ".templates")
+        WEB_TEMPLATE = os.path.join(BASE, "web")
+        JINJA_TEMPLATE = os.path.join(BASE, "jinja")
+        CODE = "code"
+
+class Next(SimpleNamespace):
+    CONFIG_FILE = "next.config.js"
+    SITEMAP_CONFIG_FILE = os.path.join(Dirs.WEB, "next-sitemap.config.js")
+    NODE_MODULES = "node_modules"
+    PACKAGE_LOCK = "package-lock.json"
+    FRONTEND_LISTENING_REGEX = "Local:[\\s]+(.*)"
+
+class ColorMode(SimpleNamespace):
+    NAME = "colorMode"
+    USE = "useColorMode"
+    TOGGLE = "toggleColorMode"
+
+class Env(str, Enum):
+    DEV = "dev"
+    PROD = "prod"
+
+class LogLevel(str, Enum):
+    DEBUG = "debug"
+    INFO = "info"
+    WARNING = "warning"
+    ERROR = "error"
+    CRITICAL = "critical"
+
+POLLING_MAX_HTTP_BUFFER_SIZE = 1000 * 1000
+
+class Ping(SimpleNamespace):
+    INTERVAL = 25
+    TIMEOUT = 120
+
+COOKIES = "cookies"
+LOCAL_STORAGE = "local_storage"
+SKIP_COMPILE_ENV_VAR = "__REFLEX_SKIP_COMPILE"
+ENV_MODE_ENV_VAR = "REFLEX_ENV_MODE"
+PYTEST_CURRENT_TEST = "PYTEST_CURRENT_TEST"
+RELOAD_CONFIG = "__REFLEX_RELOAD_CONFIG"
+REFLEX_VAR_OPENING_TAG = "<reflex.Var>"
+REFLEX_VAR_CLOSING_TAG = "</reflex.Var>"

+ 1 - 1
reflex/custom_components/custom_components.py

@@ -145,7 +145,7 @@ def _populate_demo_app(name_variants: NameVariants):
 
     with set_directory(demo_app_dir):
         # We start with the blank template as basis.
-        _init(name=demo_app_name, template=constants.Templates.Kind.BLANK)
+        _init(name=demo_app_name, template=constants.Templates.DEFAULT)
         # Then overwrite the app source file with the one we want for testing custom components.
         # This source file is rendered using jinja template file.
         with open(f"{demo_app_name}/{demo_app_name}.py", "w") as f:

+ 5 - 30
reflex/reflex.py

@@ -63,7 +63,7 @@ def main(
 
 def _init(
     name: str,
-    template: constants.Templates.Kind | None = constants.Templates.Kind.BLANK,
+    template: str | None = None,
     loglevel: constants.LogLevel = config.loglevel,
 ):
     """Initialize a new Reflex app in the given directory."""
@@ -79,31 +79,20 @@ def _init(
     app_name = prerequisites.validate_app_name(name)
     console.rule(f"[bold]Initializing {app_name}")
 
+    # Check prerequisites.
     prerequisites.check_latest_package_version(constants.Reflex.MODULE_NAME)
-
     prerequisites.initialize_reflex_user_directory()
-
     prerequisites.ensure_reflex_installation_id()
 
     # When upgrading to 0.4, show migration instructions.
     if prerequisites.should_show_rx_chakra_migration_instructions():
         prerequisites.show_rx_chakra_migration_instructions()
 
-    # Set up the app directory, only if the config doesn't exist.
-    if not os.path.exists(constants.Config.FILE):
-        if template is None:
-            template = prerequisites.prompt_for_template()
-        prerequisites.create_config(app_name)
-        prerequisites.initialize_app_directory(app_name, template)
-        telemetry_event = "init"
-    else:
-        telemetry_event = "reinit"
-
     # Set up the web project.
     prerequisites.initialize_frontend_dependencies()
 
-    # Send the telemetry event after the .web folder is initialized.
-    telemetry.send(telemetry_event)
+    # Initialize the app.
+    prerequisites.initialize_app(app_name, template)
 
     # Migrate Pynecone projects to Reflex.
     prerequisites.migrate_to_reflex()
@@ -123,7 +112,7 @@ def init(
     name: str = typer.Option(
         None, metavar="APP_NAME", help="The name of the app to initialize."
     ),
-    template: constants.Templates.Kind = typer.Option(
+    template: str = typer.Option(
         None,
         help="The template to initialize the app with.",
     ),
@@ -576,20 +565,6 @@ def demo(
     # Open the demo app in a terminal.
     webbrowser.open("https://demo.reflex.run")
 
-    # Later: open the demo app locally.
-    # with tempfile.TemporaryDirectory() as tmp_dir:
-    #     os.chdir(tmp_dir)
-    #     _init(
-    #         name="reflex_demo",
-    #         template=constants.Templates.Kind.DEMO,
-    #         loglevel=constants.LogLevel.DEBUG,
-    #     )
-    #     _run(
-    #         frontend_port=frontend_port,
-    #         backend_port=backend_port,
-    #         loglevel=constants.LogLevel.DEBUG,
-    #     )
-
 
 cli.add_typer(db_cli, name="db", help="Subcommands for managing the database schema.")
 cli.add_typer(script_cli, name="script", help="Subcommands running helper scripts.")

+ 1 - 1
reflex/testing.py

@@ -223,7 +223,7 @@ class AppHarness:
             with chdir(self.app_path):
                 reflex.reflex._init(
                     name=self.app_name,
-                    template=reflex.constants.Templates.Kind.BLANK,
+                    template=reflex.constants.Templates.DEFAULT,
                     loglevel=reflex.constants.LogLevel.INFO,
                 )
                 self.app_module_path.write_text(source_code)

+ 6 - 4
reflex/utils/console.py

@@ -2,8 +2,6 @@
 
 from __future__ import annotations
 
-from typing import List, Optional
-
 from rich.console import Console
 from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn
 from rich.prompt import Prompt
@@ -150,7 +148,10 @@ def error(msg: str, **kwargs):
 
 
 def ask(
-    question: str, choices: Optional[List[str]] = None, default: Optional[str] = None
+    question: str,
+    choices: list[str] | None = None,
+    default: str | None = None,
+    show_choices: bool = True,
 ) -> str:
     """Takes a prompt question and optionally a list of choices
      and returns the user input.
@@ -159,11 +160,12 @@ def ask(
         question: The question to ask the user.
         choices: A list of choices to select from.
         default: The default option selected.
+        show_choices: Whether to show the choices.
 
     Returns:
         A string with the user input.
     """
-    return Prompt.ask(question, choices=choices, default=default)  # type: ignore
+    return Prompt.ask(question, choices=choices, default=default, show_choices=show_choices)  # type: ignore
 
 
 def progress():

+ 223 - 22
reflex/utils/prerequisites.py

@@ -10,6 +10,7 @@ import os
 import platform
 import random
 import re
+import shutil
 import stat
 import sys
 import tempfile
@@ -30,6 +31,7 @@ from redis.asyncio import Redis
 
 import reflex
 from reflex import constants, model
+from reflex.base import Base
 from reflex.compiler import templates
 from reflex.config import Config, get_config
 from reflex.utils import console, path_ops, processes
@@ -37,6 +39,15 @@ from reflex.utils import console, path_ops, processes
 CURRENTLY_INSTALLING_NODE = False
 
 
+class Template(Base):
+    """A template for a Reflex app."""
+
+    name: str
+    description: str
+    code_url: str
+    demo_url: str
+
+
 def check_latest_package_version(package_name: str):
     """Check if the latest version of the package is installed.
 
@@ -407,17 +418,42 @@ def initialize_requirements_txt():
         console.info(f"Unable to check {fp} for reflex dependency.")
 
 
-def initialize_app_directory(app_name: str, template: constants.Templates.Kind):
+def initialize_app_directory(
+    app_name: str,
+    template_name: str = constants.Templates.DEFAULT,
+    template_code_dir_name: str | None = None,
+    template_dir: Path | None = None,
+):
     """Initialize the app directory on reflex init.
 
     Args:
         app_name: The name of the app.
-        template: The template to use.
+        template_name: The name of the template to use.
+        template_code_dir_name: The name of the code directory in the template.
+        template_dir: The directory of the template source files.
+
+    Raises:
+        Exit: If template_name, template_code_dir_name, template_dir combination is not supported.
     """
     console.log("Initializing the app directory.")
 
-    # Copy the template to the current directory.
-    template_dir = Path(constants.Templates.Dirs.BASE, "apps", template.value)
+    # By default, use the blank template from local assets.
+    if template_name == constants.Templates.DEFAULT:
+        if template_code_dir_name is not None or template_dir is not None:
+            console.error(
+                f"Only {template_name=} should be provided, got {template_code_dir_name=}, {template_dir=}."
+            )
+            raise typer.Exit(1)
+        template_code_dir_name = constants.Templates.Dirs.CODE
+        template_dir = Path(constants.Templates.Dirs.BASE, "apps", template_name)
+    else:
+        if template_code_dir_name is None or template_dir is None:
+            console.error(
+                f"For `{template_name}` template, `template_code_dir_name` and `template_dir` should both be provided."
+            )
+            raise typer.Exit(1)
+
+    console.debug(f"Using {template_name=} {template_dir=} {template_code_dir_name=}.")
 
     # Remove all pyc and __pycache__ dirs in template directory.
     for pyc_file in template_dir.glob("**/*.pyc"):
@@ -430,16 +466,16 @@ def initialize_app_directory(app_name: str, template: constants.Templates.Kind):
         path_ops.cp(str(file), file.name)
 
     # Rename the template app to the app name.
-    path_ops.mv(constants.Templates.Dirs.CODE, app_name)
+    path_ops.mv(template_code_dir_name, app_name)
     path_ops.mv(
-        os.path.join(app_name, template_dir.name + constants.Ext.PY),
+        os.path.join(app_name, template_name + constants.Ext.PY),
         os.path.join(app_name, app_name + constants.Ext.PY),
     )
 
     # Fix up the imports.
     path_ops.find_replace(
         app_name,
-        f"from {constants.Templates.Dirs.CODE}",
+        f"from {template_name}",
         f"from {app_name}",
     )
 
@@ -999,33 +1035,35 @@ def check_schema_up_to_date():
                 )
 
 
-def prompt_for_template() -> constants.Templates.Kind:
+def prompt_for_template(templates: list[Template]) -> str:
     """Prompt the user to specify a template.
 
+    Args:
+        templates: The templates to choose from.
+
     Returns:
-        The template the user selected.
+        The template name the user selects.
     """
-    # Show the user the URLs of each temlate to preview.
+    # Show the user the URLs of each template to preview.
     console.print("\nGet started with a template:")
-    console.print("blank (https://blank-template.reflex.run) - A minimal template.")
-    console.print(
-        "sidebar (https://sidebar-template.reflex.run) - A template with a sidebar to navigate pages."
-    )
-    console.print("")
 
     # Prompt the user to select a template.
+    id_to_name = {
+        str(idx): f"{template.name} ({template.demo_url}) - {template.description}"
+        for idx, template in enumerate(templates)
+    }
+    for id in range(len(id_to_name)):
+        console.print(f"({id}) {id_to_name[str(id)]}")
+
     template = console.ask(
         "Which template would you like to use?",
-        choices=[
-            template.value
-            for template in constants.Templates.Kind
-            if template.value != "demo"
-        ],
-        default=constants.Templates.Kind.BLANK.value,
+        choices=[str(i) for i in range(len(id_to_name))],
+        show_choices=False,
+        default="0",
     )
 
     # Return the template.
-    return constants.Templates.Kind(template)
+    return templates[int(template)].name
 
 
 def should_show_rx_chakra_migration_instructions() -> bool:
@@ -1178,3 +1216,166 @@ def migrate_to_reflex():
                 for old, new in updates.items():
                     line = line.replace(old, new)
                 print(line, end="")
+
+
+def fetch_app_templates() -> dict[str, Template]:
+    """Fetch the list of app templates from the Reflex backend server.
+
+    Returns:
+        The name and download URL as a dictionary.
+    """
+    config = get_config()
+    if not config.cp_backend_url:
+        console.info(
+            "Skip fetching App templates. No backend URL is specified in the config."
+        )
+        return {}
+    try:
+        response = httpx.get(
+            f"{config.cp_backend_url}{constants.Templates.APP_TEMPLATES_ROUTE}"
+        )
+        response.raise_for_status()
+        return {
+            template["name"]: Template.parse_obj(template)
+            for template in response.json()
+        }
+    except httpx.HTTPError as ex:
+        console.info(f"Failed to fetch app templates: {ex}")
+        return {}
+    except (TypeError, KeyError, json.JSONDecodeError) as tkje:
+        console.info(f"Unable to process server response for app templates: {tkje}")
+        return {}
+
+
+def create_config_init_app_from_remote_template(
+    app_name: str,
+    template_url: str,
+):
+    """Create new rxconfig and initialize app using a remote template.
+
+    Args:
+        app_name: The name of the app.
+        template_url: The path to the template source code as a zip file.
+
+    Raises:
+        Exit: If any download, file operations fail or unexpected zip file format.
+
+    """
+    # Create a temp directory for the zip download.
+    try:
+        temp_dir = tempfile.mkdtemp()
+    except OSError as ose:
+        console.error(f"Failed to create temp directory for download: {ose}")
+        raise typer.Exit(1) from ose
+
+    # Use httpx GET with redirects to download the zip file.
+    zip_file_path = Path(temp_dir) / "template.zip"
+    try:
+        # Note: following redirects can be risky. We only allow this for reflex built templates at the moment.
+        response = httpx.get(template_url, follow_redirects=True)
+        console.debug(f"Server responded download request: {response}")
+        response.raise_for_status()
+    except httpx.HTTPError as he:
+        console.error(f"Failed to download the template: {he}")
+        raise typer.Exit(1) from he
+    try:
+        with open(zip_file_path, "wb") as f:
+            f.write(response.content)
+            console.debug(f"Downloaded the zip to {zip_file_path}")
+    except OSError as ose:
+        console.error(f"Unable to write the downloaded zip to disk {ose}")
+        raise typer.Exit(1) from ose
+
+    # Create a temp directory for the zip extraction.
+    try:
+        unzip_dir = Path(tempfile.mkdtemp())
+    except OSError as ose:
+        console.error(f"Failed to create temp directory for extracting zip: {ose}")
+        raise typer.Exit(1) from ose
+    try:
+        zipfile.ZipFile(zip_file_path).extractall(path=unzip_dir)
+        # The zip file downloaded from github looks like:
+        # repo-name-branch/**/*, so we need to remove the top level directory.
+        if len(subdirs := os.listdir(unzip_dir)) != 1:
+            console.error(f"Expected one directory in the zip, found {subdirs}")
+            raise typer.Exit(1)
+        template_dir = unzip_dir / subdirs[0]
+        console.debug(f"Template folder is located at {template_dir}")
+    except Exception as uze:
+        console.error(f"Failed to unzip the template: {uze}")
+        raise typer.Exit(1) from uze
+
+    # Move the rxconfig file here first.
+    path_ops.mv(str(template_dir / constants.Config.FILE), constants.Config.FILE)
+    new_config = get_config(reload=True)
+
+    # Get the template app's name from rxconfig in case it is different than
+    # the source code repo name on github.
+    template_name = new_config.app_name
+
+    create_config(app_name)
+    initialize_app_directory(
+        app_name,
+        template_name=template_name,
+        template_code_dir_name=template_name,
+        template_dir=template_dir,
+    )
+
+    #  Clean up the temp directories.
+    shutil.rmtree(temp_dir)
+    shutil.rmtree(unzip_dir)
+
+
+def initialize_app(app_name: str, template: str | None = None):
+    """Initialize the app either from a remote template or a blank app. If the config file exists, it is considered as reinit.
+
+    Args:
+        app_name: The name of the app.
+        template: The name of the template to use.
+
+    Raises:
+        Exit: If template is directly provided in the command flag and is invalid.
+    """
+    # Local imports to avoid circular imports.
+    from reflex.utils import telemetry
+
+    # Check if the app is already initialized.
+    if os.path.exists(constants.Config.FILE):
+        telemetry.send("reinit")
+        return
+
+    # Get the available templates
+    templates: dict[str, Template] = fetch_app_templates()
+
+    # Prompt for a template if not provided.
+    if template is None and len(templates) > 0:
+        template = prompt_for_template(list(templates.values()))
+    elif template is None:
+        template = constants.Templates.DEFAULT
+    assert template is not None
+
+    # If the blank template is selected, create a blank app.
+    if template == constants.Templates.DEFAULT:
+        # Default app creation behavior: a blank app.
+        create_config(app_name)
+        initialize_app_directory(app_name)
+    else:
+        # Fetch App templates from the backend server.
+        console.debug(f"Available templates: {templates}")
+
+        # If user selects a template, it needs to exist.
+        if template in templates:
+            template_url = templates[template].code_url
+        else:
+            # Check if the template is a github repo.
+            if template.startswith("https://github.com"):
+                template_url = f"{template.strip('/')}/archive/main.zip"
+            else:
+                console.error(f"Template `{template}` not found.")
+                raise typer.Exit(1)
+        create_config_init_app_from_remote_template(
+            app_name=app_name,
+            template_url=template_url,
+        )
+
+    telemetry.send("init")