Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ repos:
rev: v0.15.5
hooks:
- id: ruff-check
files: ^reflex_ui/
files: ^(reflex_ui|shared)/
args: ["--fix", "--exit-non-zero-on-fix", "--no-unsafe-fixes"]
- id: ruff-format
files: ^reflex_ui/
files: ^(reflex_ui|shared)/

- repo: https://github.com/codespell-project/codespell
rev: v2.4.2
hooks:
- id: codespell
files: ^reflex_ui/
files: ^(reflex_ui|shared)/

# Run pyi check before pyright because pyright can fail if pyi files are wrong.
# - repo: local
Expand All @@ -31,5 +31,5 @@ repos:
rev: v1.1.408
hooks:
- id: pyright
files: ^reflex_ui/
files: ^(reflex_ui|shared)/
language: system
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ lint.pydocstyle.convention = "google"
"*.pyi" = ["D301", "D415", "D417", "D418", "E742", "N", "PGH"]
"**/alembic/*.py" = ["D", "ERA"]
"__init__.py" = ["ERA"]
"shared/**" = ["D100", "D101", "D102", "D103", "D104", "T201"]

[tool.pyright]
reportIncompatibleMethodOverride = false
Expand Down
Empty file added shared/__init__.py
Empty file.
Empty file added shared/backend/__init__.py
Empty file.
37 changes: 37 additions & 0 deletions shared/backend/get_blogs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from typing import TypedDict

import httpx
import reflex as rx

from shared.constants import RECENT_BLOGS_API_URL


class BlogPostDict(TypedDict):
title: str
description: str
author: str
date: str
image: str
tag: str
url: str


class RecentBlogsState(rx.State):
posts: rx.Field[list[BlogPostDict]] = rx.field(default_factory=list)
_fetched: bool = False

@rx.event(background=True, temporal=True)
async def fetch_recent_blogs(self):
if self._fetched:
return
try:
async with httpx.AsyncClient() as client:
resp = await client.get(RECENT_BLOGS_API_URL, timeout=10)
resp.raise_for_status()
data = resp.json()
async with self:
self.posts = data.get("posts", [])
self._fetched = True
except Exception:
async with self:
self.posts = []
111 changes: 111 additions & 0 deletions shared/backend/signup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import contextlib
import os
from datetime import datetime
from typing import Any

import httpx
import reflex as rx
from email_validator import EmailNotValidError, ValidatedEmail, validate_email
from sqlmodel import Field

from shared.constants import (
API_BASE_URL_LOOPS,
REFLEX_DEV_WEB_NEWSLETTER_FORM_WEBHOOK_URL,
)


class Waitlist(rx.Model, table=True):
email: str
date_created: datetime = Field(default_factory=datetime.utcnow, nullable=False)


class IndexState(rx.State):
"""Hold the state for the home page."""

# Whether the user signed up for the newsletter.
signed_up: bool = False

# Whether to show the confetti.
show_confetti: bool = False

@rx.event(background=True)
async def send_contact_to_webhook(
self,
email: str | None,
) -> None:
with contextlib.suppress(httpx.HTTPError):
async with httpx.AsyncClient() as client:
response = await client.post(
REFLEX_DEV_WEB_NEWSLETTER_FORM_WEBHOOK_URL,
json={
"email": email,
},
)
response.raise_for_status()

@rx.event(background=True)
async def add_contact_to_loops(
self,
email: str | None,
):
url: str = f"{API_BASE_URL_LOOPS}/contacts/create"
loops_api_key: str | None = os.getenv("LOOPS_API_KEY")
if loops_api_key is None:
print("Loops API key does not exist")
return

headers = {
"Accept": "application/json",
"Authorization": f"Bearer {loops_api_key}",
}
try:
async with httpx.AsyncClient() as client:
response = await client.post(
url,
headers=headers,
json={
"email": email,
},
)
response.raise_for_status() # Raise an exception for HTTP errors (4xx and 5xx)

except httpx.HTTPError as e:
print(f"An error occurred: {e}")

@rx.event
def signup_for_another_user(self):
self.signed_up = False

@rx.event(background=True)
async def signup(
self,
form_data: dict[str, Any],
):
"""Sign the user up for the newsletter."""
email: str | None = None
if email_to_validate := form_data.get("input_email"):
try:
validated_email: ValidatedEmail = validate_email(
email_to_validate,
check_deliverability=True,
)
email = validated_email.normalized

except EmailNotValidError as e:
# Alert the error message.
yield rx.toast.warning(
str(e),
style={
"border": "1px solid #3C3646",
"background": "linear-gradient(218deg, #1D1B23 -35.66%, #131217 100.84%)",
},
)
return
yield IndexState.send_contact_to_webhook(email)
yield IndexState.add_contact_to_loops(email)
async with self:
self.signed_up = True
yield
yield [
rx.toast.success("Thanks for signing up to the Newsletter!"),
]
Empty file added shared/components/__init__.py
Empty file.
4 changes: 4 additions & 0 deletions shared/components/blocks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .code import *
from .demo import *
from .headings import *
from .typography import *
93 changes: 93 additions & 0 deletions shared/components/blocks/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""Code block components for documentation pages."""

import reflex as rx

import shared.styles.fonts as fonts
from shared import styles


@rx.memo
def code_block(code: str, language: str):
return rx.box(
rx._x.code_block(
code,
language=language,
class_name="code-block",
can_copy=True,
),
class_name="relative mb-4",
)


@rx.memo
def code_block_dark(code: str, language: str):
return rx.box(
rx._x.code_block(
code,
language=language,
class_name="code-block",
can_copy=True,
),
class_name="relative mb-4",
)


def code_block_markdown(*children, **props):
language = props.get("language", "plain")
return code_block(code=children[0], language=language)


def code_block_markdown_dark(*children, **props):
language = props.get("language", "plain")
return code_block_dark(code=children[0], language=language)


def doccmdoutput(
command: str,
output: str,
) -> rx.Component:
"""Create a documentation code snippet.

Args:
command: The command to display.
output: The output of the command.
theme: The theme of the component.

Returns:
The styled command and its example output.
"""
return rx.vstack(
rx._x.code_block(
command,
can_copy=True,
border_radius=styles.DOC_BORDER_RADIUS,
background="transparent",
theme="ayu-dark",
language="bash",
code_tag_props={
"style": {
"fontFamily": "inherit",
}
},
style=fonts.code,
font_family="JetBrains Mono",
width="100%",
),
rx._x.code_block(
output,
can_copy=False,
border_radius="12px",
background="transparent",
theme="ayu-dark",
language="log",
code_tag_props={
"style": {
"fontFamily": "inherit",
}
},
style=fonts.code,
font_family="JetBrains Mono",
width="100%",
),
padding_y="1em",
)
58 changes: 58 additions & 0 deletions shared/components/blocks/collapsible.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Collapsible accordion box used by alert and video blocks."""

from collections.abc import Sequence

import reflex as rx
from reflex_core.constants.colors import ColorType


def collapsible_box(
trigger_children: Sequence[rx.Component],
body: rx.Component,
color: ColorType,
*,
item_border_radius: str = "12px",
) -> rx.Component:
"""Collapsible accordion wrapper shared by alert and video directives."""
return rx.box(
rx.accordion.root(
rx.accordion.item(
rx.accordion.header(
rx.accordion.trigger(
rx.hstack(
*trigger_children,
rx.spacer(),
rx.accordion.icon(color=f"{rx.color(color, 11)}"),
align_items="center",
justify_content="left",
text_align="left",
spacing="2",
width="100%",
),
padding="0px",
color=f"{rx.color(color, 11)} !important",
background_color="transparent !important",
border_radius="12px",
_hover={},
),
),
body,
border_radius=item_border_radius,
padding=["16px", "24px"],
background_color=f"{rx.color(color, 3)}",
border="none",
),
background="transparent !important",
border_radius="12px",
box_shadow="none !important",
collapsible=True,
width="100%",
),
border=f"1px solid {rx.color(color, 4)}",
border_radius="12px",
background_color=f"{rx.color(color, 3)} !important",
width="100%",
margin_bottom="16px",
margin_top="16px",
overflow="hidden",
)
Loading
Loading