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
12 changes: 7 additions & 5 deletions form-designer/form_designer/components/field_editor.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""Edit Field modal."""

from typing import Any
import reflex as rx

from .. import constants, routes, utils
Expand All @@ -19,7 +21,7 @@ class FieldEditorState(AppState):
def _user_has_access(self):
return self.form_owner_id == self.authenticated_user.id or self.is_admin

def handle_submit(self, form_data: dict[str, str]):
def handle_submit(self, form_data: dict[str, Any]):
self.field.name = form_data["field_name"]
self.field.type_ = form_data["type_"]
self.field.required = bool(form_data.get("required"))
Expand Down Expand Up @@ -266,20 +268,20 @@ def field_editor_modal():
def field_edit_title():
form_name = rx.cond(
rx.State.form_id == "",
utils.quoted_var("New Form"),
"New Form",
rx.cond(
FormEditorState.form,
FormEditorState.form.name,
utils.quoted_var("Unknown Form"),
"Unknown Form",
),
)
field_name = rx.cond(
rx.State.field_id == "",
utils.quoted_var("New Field"),
"New Field",
rx.cond(
FieldEditorState.field,
FieldEditorState.field.name,
utils.quoted_var("Unknown Field"),
"Unknown Field",
),
)
return f"{constants.TITLE} | {form_name} | {field_name}"
34 changes: 20 additions & 14 deletions form-designer/form_designer/components/field_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
class OptionItemCallable:
def __call__(
self, *children: rx.Component, value: rx.Var[str], **props
) -> rx.Component:
...
) -> rx.Component: ...


def option_value_label_id(option: Option) -> rx.Component:
Expand Down Expand Up @@ -51,9 +50,12 @@ def field_select(field: Field) -> rx.Component:


def radio_item(*children: rx.Component, value: rx.Var[str], **props) -> rx.Component:
return rx.hstack(
rx.radio.item(value=value, **props),
*children,
return rx.el.label(
rx.hstack(
rx.radio.item(value=value, **props),
*children,
align="center",
),
)


Expand Down Expand Up @@ -132,14 +134,18 @@ def field_prompt(field: Field, show_name: bool = False):
)


def field_view(field: Field):
return rx.card(
rx.hstack(
field_prompt(field),
rx.text(rx.cond(field.required, "*", "")),
),
rx.hstack(
field_input(field),
flex_wrap="wrap",
def field_view(field: Field, *children: rx.Component, card_props: dict | None = None):
return rx.form.field(
rx.card(
rx.hstack(
field_prompt(field),
rx.text(rx.cond(field.required, "*", "")),
),
rx.hstack(
field_input(field),
flex_wrap="wrap",
),
*children,
**(card_props or {}),
),
)
6 changes: 3 additions & 3 deletions form-designer/form_designer/components/form_editor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import reflex as rx

from .. import constants, routes, utils
from .. import constants, routes
from ..models import Field, Form
from ..state import AppState
from .field_view import field_input, field_prompt
Expand Down Expand Up @@ -160,11 +160,11 @@ def form_editor():
def form_edit_title():
form_name = rx.cond(
rx.State.form_id == "",
utils.quoted_var("New Form"),
"New Form",
rx.cond(
FormEditorState.form,
FormEditorState.form.name,
utils.quoted_var("Unknown Form"),
"Unknown Form",
),
)
return f"{constants.TITLE} | {form_name}"
9 changes: 4 additions & 5 deletions form-designer/form_designer/form_designer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import reflex_local_auth

from . import constants, routes, utils
from . import constants, routes
from .components import (
FieldEditorState,
FormEditorState,
Expand All @@ -24,9 +24,8 @@
app = rx.App(theme=rx.theme(accent_color="blue"))
app.add_page(home_page, route="/", title=constants.TITLE)

# Adding a dummy route to register the dynamic route vars.
with contextlib.suppress(ValueError):
app.add_page(lambda: rx.fragment(on_click=rx.event.noop()), route="/_dummy/[form_id]/[field_id]")
# Register the dynamic route vars.
rx.State.setup_dynamic_args(rx.app.get_route_args("/_dummy/[form_id]/[field_id]"))

# Authentication via reflex-local-auth
app.add_page(
Expand Down Expand Up @@ -74,7 +73,7 @@
route=routes.FORM_ENTRY,
title=rx.cond(
rx.State.form_id == "",
utils.quoted_var("Unknown Form"),
"Unknown Form",
FormEntryState.form.name,
),
on_load=FormEntryState.load_form,
Expand Down
3 changes: 0 additions & 3 deletions form-designer/form_designer/pages/form_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from ..components import navbar, form_select, form_editor, field_editor_modal



@utils.require_login
def form_editor_page() -> rx.Component:
return style.layout(
Expand Down Expand Up @@ -33,5 +32,3 @@ def form_editor_page() -> rx.Component:
),
rx.logo(height="3em", margin_bottom="12px"),
)


44 changes: 39 additions & 5 deletions form-designer/form_designer/pages/form_entry.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from typing import Any
import reflex as rx

from reflex_local_auth import LocalAuthState

from .. import routes, style
from ..components import field_view, navbar
from ..models import FieldType, FieldValue, Form, Response
from ..models import Field, FieldType, FieldValue, Form, Response


Missing = object()
Expand All @@ -13,13 +14,15 @@
class FormEntryState(rx.State):
form: Form = Form()
client_token: str = rx.Cookie("")
missing_fields: dict[str, bool] = {}

def _ensure_client_token(self):
if self.client_token == "":
self.client_token = self.router.session.client_token
return self.client_token

def load_form(self):
self.missing_fields = {}
if self.form_id != "":
self.load_form_by_id(self.form_id)
else:
Expand All @@ -29,27 +32,39 @@ def load_form_by_id(self, id_: int):
with rx.session() as session:
self.form = session.get(Form, id_)

def handle_submit(self, form_data):
def handle_submit(self, form_data: dict[str, Any]):
self.missing_fields = {}
response = Response(
client_token=self._ensure_client_token(), form_id=self.form.id
)
for field in self.form.fields:
value = form_data.get(field.name, Missing)
if value is not Missing:
if value and value is not Missing:
response.field_values.append(
FieldValue(
field_id=field.id,
value=value,
)
)
elif field.type_ == FieldType.checkbox:
field_values = []
for option in field.options:
key = f"{field.name}___{option.value or option.label or option.id}"
value = form_data.get(key, Missing)
if value is not Missing:
response.field_values.append(
field_values.append(
FieldValue(field_id=field.id, value=form_data[key])
)
if field.required and not field_values:
self.missing_fields[field.prompt or field.name] = True
elif field.required:
self.missing_fields[field.prompt or field.name] = True
if self.missing_fields:
if len(self.missing_fields) == 1:
return rx.toast(
f"Required field '{tuple(self.missing_fields)[0]}' is missing a response"
)
return rx.toast("Multiple required fields are missing a response")
with rx.session() as session:
session.add(response)
session.commit()
Expand All @@ -66,6 +81,25 @@ def authenticated_navbar(title_suffix: str | None = None):
)


def validated_field_view(field: Field) -> rx.Component:
return field_view(
field,
rx.form.message(
"This field is required.",
match="valueMissing",
force_match=FormEntryState.missing_fields[field.name],
color=rx.color("tomato", 10),
),
card_props={
"--base-card-surface-box-shadow": rx.cond(
FormEntryState.missing_fields[field.name],
f"0 0 0 1px {rx.color('tomato', 10)}",
"inherit",
),
},
)


def form_entry_page():
return style.layout(
authenticated_navbar(title_suffix=f"Preview {FormEntryState.form.id}"),
Expand All @@ -74,7 +108,7 @@ def form_entry_page():
rx.center(rx.heading(FormEntryState.form.name)),
rx.foreach(
FormEntryState.form.fields,
field_view,
validated_field_view,
),
rx.button("Submit", type="submit"),
),
Expand Down
6 changes: 6 additions & 0 deletions form-designer/form_designer/pages/home.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import importlib.metadata
from pathlib import Path

import reflex as rx
Expand All @@ -22,4 +23,9 @@ def home_page() -> rx.Component:
rx.markdown(readme_content.read_text()),
margin_y="2em",
),
rx.hstack(
rx.logo(),
rx.text(f"v{importlib.metadata.version('reflex')}", size="1"),
align="center",
),
)
2 changes: 1 addition & 1 deletion form-designer/form_designer/pages/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def response(r: Response):
def responses_title():
form_name = rx.cond(
rx.State.form_id == "",
utils.quoted_var("Unknown Form"),
"Unknown Form",
ResponsesState.form.name,
)
return f"{constants.TITLE} | {form_name} | Responses"
Expand Down
5 changes: 0 additions & 5 deletions form-designer/form_designer/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,6 @@
from reflex_local_auth import LoginState


def quoted_var(value: str) -> rx.Var:
"""Allows a bare string to be used in a page title with other Vars."""
return rx.Var.create_safe(f"'{value}'", _var_is_string=False, _var_is_local=True)


def require_login(page: rx.app.ComponentCallable) -> rx.app.ComponentCallable:
"""Decorator to require authentication before rendering a page.

Expand Down