// browser-auth.el -- El-compiled auth flow using Supabase via native_js_call // // Compile: elc --target=js --bundle examples/browser-auth.el > auth.js // (requires el_runtime.js in the same directory as browser-auth.el) // // Demonstrates: // - @async functions with DOM interaction // - native_js_call to invoke third-party library methods (Supabase) // - DOM bridge: dom_get_element, dom_get_value, dom_set_text, dom_add_class // dom_remove_class, dom_show, dom_hide, dom_is_null // - window_set to expose El functions to the browser global scope // - local_storage_set/get for session hints // - set_timeout for transient UI state // - state_set/get for component state // // Expected HTML elements: // #acct-email-input -- email text input // #send-link-btn -- submit button // #auth-message -- status message container // #auth-form -- the form to hide after success // // Supabase client is expected on window.supabase (loaded from Supabase CDN // via a separate script tag before auth.js). // ── UI helpers ───────────────────────────────────────────────────────────── fn show_message(text: String, is_error: Bool) -> Void { let msg_el = dom_get_element("auth-message") if !dom_is_null(msg_el) { dom_set_text(msg_el, text) dom_remove_class(msg_el, "hidden") if is_error { dom_add_class(msg_el, "error") dom_remove_class(msg_el, "success") } else { dom_add_class(msg_el, "success") dom_remove_class(msg_el, "error") } } } fn set_button_loading(loading: Bool) -> Void { let btn = dom_get_element("send-link-btn") if !dom_is_null(btn) { if loading { dom_set_text(btn, "Sending...") dom_set_attr(btn, "disabled", "true") } else { dom_set_text(btn, "Send Magic Link") dom_remove_attr(btn, "disabled") } } } fn clear_message() -> Void { let msg_el = dom_get_element("auth-message") if !dom_is_null(msg_el) { dom_add_class(msg_el, "hidden") dom_set_text(msg_el, "") } } // ── Email validation ─────────────────────────────────────────────────────── fn is_valid_email(email: String) -> Bool { let trimmed: String = str_trim(email) if str_len(trimmed) < 5 { return false } let at_pos: Int = str_index_of(trimmed, "@") if at_pos < 1 { return false } let dot_pos: Int = str_index_of(trimmed, ".") if dot_pos < at_pos + 2 { return false } return true } // ── Auth flow ────────────────────────────────────────────────────────────── @async fn send_magic_link() -> Void { let email_el = dom_get_element("acct-email-input") if dom_is_null(email_el) { show_message("Could not find email input", true) return null } let email: String = str_trim(dom_get_value(email_el)) if !is_valid_email(email) { show_message("Please enter a valid email address", true) return null } clear_message() set_button_loading(true) state_set("auth_email", email) // Call Supabase via native_js_call. // window.supabase.auth.signInWithOtp({ email: "..." }) returns a Promise. let supabase = window_get("supabase") if dom_is_null(supabase) { show_message("Auth service not available", true) set_button_loading(false) return null } let auth = native_js_call(supabase, "auth", []) let payload = { "email": email } let result = native_js_call(auth, "signInWithOtp", [payload]) // Await the promise via native_js_call on the result object let error = native_js_call(result, "then", [check_auth_result]) set_button_loading(false) } fn check_auth_result(resp: Any) -> Void { let err = resp["error"] if !dom_is_null(err) { let msg: String = err["message"] show_message("Error: " + msg, true) return null } let email: String = state_get("auth_email") local_storage_set("auth_pending_email", email) show_message("Magic link sent! Check your inbox for " + email, false) let form = dom_get_element("auth-form") if !dom_is_null(form) { dom_hide(form) } } // ── Keyboard support ─────────────────────────────────────────────────────── fn handle_email_keydown(event: Any) -> Void { let key: String = dom_get_prop(event, "key") if str_eq(key, "Enter") { send_magic_link() } } // ── Initialization ───────────────────────────────────────────────────────── fn init_auth() -> Void { let email_el = dom_get_element("acct-email-input") if !dom_is_null(email_el) { // Pre-fill from local storage if a pending send was interrupted let pending: String = local_storage_get("auth_pending_email") if !str_eq(pending, "") { dom_set_value(email_el, pending) } dom_listen(email_el, "keydown", handle_email_keydown) } let btn = dom_get_element("send-link-btn") if !dom_is_null(btn) { dom_listen(btn, "click", send_magic_link) } state_set("auth_initialized", "true") } fn main() -> Void { // Expose send_magic_link globally so inline event handlers can call it window_set("sendMagicLink", send_magic_link) window_set("initAuth", init_auth) // Run init when DOM is ready window_on_load(init_auth) }