1use gloo_utils::document;
2use js_sys::Reflect;
3use log::error;
4use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
5use yew::{
6 function_component, html, use_effect_with_deps, use_state, virtual_dom::AttrValue, Callback,
7 Html, Properties,
8};
9
10#[derive(Properties, PartialEq)]
11pub struct Props {
12 pub site_key: AttrValue,
13 pub on_load: Option<Callback<()>>,
14}
15
16#[function_component]
17pub fn HCaptcha(props: &Props) -> Html {
18 let loaded = use_state(|| false);
19 use_effect_with_deps(
20 move |on_load| {
21 if !*loaded {
22 if let Err(e) = inject_script(on_load) {
23 error!("{:?}", e);
24 }
25 }
26 loaded.set(true);
27 || ()
28 },
29 props.on_load.clone(),
30 );
31 html! {
32 <>
33 <div class="h-captcha" data-sitekey={props.site_key.to_string()} data-theme="dark"></div>
34 </>
35 }
36}
37
38fn inject_script(on_load: &Option<Callback<()>>) -> Result<(), JsValue> {
39 let hcaptcha_loaded = Closure::wrap(Box::new({
40 let on_load = on_load.clone();
41 move || {
42 if let Some(on_load) = &on_load {
43 on_load.emit(());
44 }
45 }
46 }) as Box<dyn FnMut()>);
47 Reflect::set(
48 &JsValue::from(web_sys::window().unwrap()),
49 &JsValue::from("hCaptchaLoaded"),
50 hcaptcha_loaded.as_ref().unchecked_ref(),
51 )?;
52 hcaptcha_loaded.forget();
53 let script = document().create_element("script").unwrap();
54 script.set_attribute("async", "true")?;
55 script.set_attribute("defer", "true")?;
56 script.set_attribute(
57 "src",
58 "https://js.hcaptcha.com/1/api.js?hl=en&onload=hCaptchaLoaded",
59 )?;
60 script.set_attribute("type", "text/javascript")?;
61 let body = document()
62 .body()
63 .ok_or(JsValue::from_str("Can't find body"))?;
64 body.append_child(&script)?;
65 Ok(())
66}