use crate::app::{dom, templates};
pub(super) fn toggle_theme() {
set_render_mode("theme-light", "lh-theme", "light", "dark");
}
pub(super) fn toggle_preview() {
set_render_mode("preview-mobile", "lh-preview", "mobile", "desktop");
}
fn set_render_mode(class: &str, key: &str, on_val: &str, off_val: &str) {
let Some(win) = web_sys::window() else { return };
let Some(html) = win.document().and_then(|d| d.document_element()) else {
return;
};
let list = html.class_list();
let next_on = !list.contains(class);
if next_on {
let _ = list.add_1(class);
} else {
let _ = list.remove_1(class);
}
if let Ok(Some(storage)) = win.local_storage() {
let _ = storage.set_item(key, if next_on { on_val } else { off_val });
}
dom::swap_outer(
"display-section",
&templates::admin_display_section().into_string(),
);
}
pub(super) fn reset_confirm_pressed() {
let typed = dom::input_by_id("reset-confirm-text")
.map(|i| i.value().trim().to_string())
.unwrap_or_default();
if !typed.eq_ignore_ascii_case("RESET") {
dom::swap_inner(
"reset-confirm-msg",
"<span style=\"color:var(--error)\">type RESET to confirm</span>",
);
return;
}
wasm_bindgen_futures::spawn_local(async move {
let fs = crate::app::shared_opfs();
if let Ok(entries) = fs.read_dir("").await {
for entry in entries {
let _ = fs.delete(&entry.name).await;
}
}
if let Ok(window) = dom::window() {
let _ = window.location().reload();
}
});
}
pub(super) fn pricing_save_pressed() {
let Some(input) = dom::input_by_id("pricing-input") else {
return;
};
let raw = input.value().trim().to_string();
let wei = match parse_eth_to_wei(&raw) {
Ok(w) => w,
Err(err) => {
dom::swap_inner(
"pricing-msg",
&dom::msg_span(dom::Msg::Error, &err.to_string()),
);
return;
}
};
let is_owner = crate::app::APP.with(|cell| {
matches!(cell.borrow().verify_state, crate::app::VerifyState::Verified { .. })
});
if !is_owner {
dom::swap_inner(
"pricing-msg",
"<span style=\"color:var(--error)\">only the verified owner can change pricing</span>",
);
return;
}
dom::swap_inner(
"pricing-msg",
"<span style=\"color:var(--muted)\">saving…</span>",
);
wasm_bindgen_futures::spawn_local(async move {
match crate::app::pricing::save(wei).await {
Ok(()) => {
crate::app::APP
.with(|cell| cell.borrow_mut().pricing_wei = Some(wei));
let html = templates::pricing_card_body(wei, true).into_string();
dom::swap_outer("pricing-body", &html);
}
Err(err) => {
dom::swap_inner(
"pricing-msg",
&dom::msg_span(dom::Msg::Error, &format!("save failed: {err}")),
);
}
}
});
}
fn parse_eth_to_wei(s: &str) -> Result<u128, String> {
if s.is_empty() {
return Ok(0);
}
let (whole_str, frac_str) = match s.split_once('.') {
Some((w, f)) => (w, f),
None => (s, ""),
};
if !whole_str.bytes().all(|b| b.is_ascii_digit()) {
return Err("price must be a positive decimal".into());
}
if !frac_str.bytes().all(|b| b.is_ascii_digit()) {
return Err("price must be a positive decimal".into());
}
if frac_str.len() > 18 {
return Err("price has more precision than wei (18 decimals max)".into());
}
let whole: u128 = whole_str.parse().map_err(|e| format!("whole: {e}"))?;
let mut padded = String::with_capacity(18);
padded.push_str(frac_str);
while padded.len() < 18 {
padded.push('0');
}
let frac: u128 = if padded.is_empty() {
0
} else {
padded.parse().map_err(|e| format!("frac: {e}"))?
};
whole
.checked_mul(1_000_000_000_000_000_000)
.and_then(|w| w.checked_add(frac))
.ok_or_else(|| "price too large".into())
}