mesop / form_billing.py
wwwillchen's picture
Commit
8e04495
from dataclasses import asdict
from typing import Literal
import mesop as me
ROW_GAP = 15
BOX_PADDING = 20
@me.stateclass
class State:
first_name: str
last_name: str
username: str
email: str
address: str
address_2: str
country: str
state: str
zip: str
payment_type: str
name_on_card: str
credit_card: str
expiration: str
cvv: str
errors: dict[str, str]
def is_mobile():
return me.viewport_size().width < 620
def calc_input_size(items: int):
return int(
(me.viewport_size().width - (ROW_GAP * items) - (BOX_PADDING * 2)) / items
)
def load(e: me.LoadEvent):
me.set_theme_density(-3)
me.set_theme_mode("system")
@me.page(
security_policy=me.SecurityPolicy(
allowed_iframe_parents=["https://google.github.io", "https://huggingface.co"]
),
path="/form_billing",
on_load=load,
)
def page():
state = me.state(State)
with me.box(
style=me.Style(
padding=me.Padding.all(BOX_PADDING),
max_width=800,
margin=me.Margin.symmetric(horizontal="auto"),
)
):
me.text(
"Billing form",
type="headline-4",
style=me.Style(margin=me.Margin(bottom=10)),
)
with form_group():
name_width = calc_input_size(2) if is_mobile() else "100%"
input_field(label="First name", width=name_width)
input_field(label="Last name", width=name_width)
with form_group():
input_field(label="Username")
with me.box(style=me.Style(display="flex", gap=ROW_GAP)):
input_field(label="Email", input_type="email")
with me.box(style=me.Style(display="flex", gap=ROW_GAP)):
input_field(label="Address")
with form_group():
input_field(label="Address 2")
with form_group():
country_state_zip_width = calc_input_size(3) if is_mobile() else "100%"
input_field(label="Country", width=country_state_zip_width)
input_field(label="State", width=country_state_zip_width)
input_field(label="Zip", width=country_state_zip_width)
divider()
me.text(
"Payment",
type="headline-4",
style=me.Style(margin=me.Margin(bottom=10)),
)
with form_group(flex_direction="column"):
me.radio(
key="payment_type",
on_change=on_change,
options=[
me.RadioOption(label="Credit card", value="credit_card"),
me.RadioOption(label="Debit card", value="debit_card"),
me.RadioOption(label="Paypal", value="paypal"),
],
style=me.Style(
display="flex", flex_direction="column", margin=me.Margin(bottom=20)
),
)
if "payment_type" in state.errors:
me.text(
state.errors["payment_type"],
style=me.Style(
margin=me.Margin(top=-30, left=5, bottom=15),
color=me.theme_var("error"),
font_size=13,
),
)
with form_group():
payments_width = calc_input_size(2) if is_mobile() else "100%"
input_field(label="Name on card", width=payments_width)
input_field(label="Credit card", width=payments_width)
with form_group():
input_field(label="Expiration", width=payments_width)
input_field(label="CVV", width=payments_width, input_type="number")
divider()
me.button(
"Continue to checkout",
type="flat",
style=me.Style(width="100%", padding=me.Padding.all(25), font_size=20),
on_click=on_click,
)
def divider():
with me.box(style=me.Style(margin=me.Margin.symmetric(vertical=20))):
me.divider()
@me.content_component
def form_group(flex_direction: Literal["row", "column"] = "row"):
with me.box(
style=me.Style(
display="flex", flex_direction=flex_direction, gap=ROW_GAP, width="100%"
)
):
me.slot()
def input_field(
*,
key: str = "",
label: str,
value: str = "",
width: str | int = "100%",
input_type: Literal[
"color",
"date",
"datetime-local",
"email",
"month",
"number",
"password",
"search",
"tel",
"text",
"time",
"url",
"week",
] = "text",
):
state = me.state(State)
key = key if key else label.lower().replace(" ", "_")
with me.box(style=me.Style(flex_grow=1, width=width)):
me.input(
key=key,
label=label,
value=value,
appearance="outline",
color="warn" if key in state.errors else "primary",
style=me.Style(width=width),
type=input_type,
on_blur=on_blur,
)
if key in state.errors:
me.text(
state.errors[key],
style=me.Style(
margin=me.Margin(top=-13, left=15, bottom=15),
color=me.theme_var("error"),
font_size=13,
),
)
def on_change(e: me.RadioChangeEvent):
state = me.state(State)
setattr(state, e.key, e.value)
def on_blur(e: me.InputBlurEvent):
state = me.state(State)
setattr(state, e.key, e.value)
def on_click(e: me.ClickEvent):
state = me.state(State)
# Replace with real validation logic.
errors = {}
for key, value in asdict(state).items(): # type: ignore
if key == "error":
continue
if not value:
errors[key] = f"{key.replace('_', ' ').capitalize()} is required"
state.errors = errors
# Replace with form processing logic.
if not state.errors:
pass