edc-web-components 0.7.1

A Rust Web Components with patternfly-yew client for EDC
Documentation
use crate::contexts::{CounterParty, use_counter_parties_context};
use crate::services::get_dsp_endpoint;
use edc_federated_catalog_client::models::FederatedCatalogParticipantCreateForm;
use edc_federated_catalog_client::{FederatedCatalogClient, FederatedCatalogClientVersion};
use edc_identity_hub_client::models::DidWeb;
use patternfly_yew::prelude::*;
use yew::platform::spawn_local;
use yew::prelude::*;
use yew_oauth2::hook::use_latest_access_token;

#[derive(Clone, Debug, PartialEq, Properties)]
pub struct CreateFederatedCatalogParticipantProps {
  #[prop_or_default]
  pub on_create: Callback<()>,
}

#[component]
pub fn CreateFederatedCatalogParticipant(props: &CreateFederatedCatalogParticipantProps) -> Html {
  let counter_parties_context = use_counter_parties_context();

  let id = use_state(|| "".to_string());
  let target_url = use_state(|| "".to_string());

  let onchange_id = use_callback(
    (id.setter(), target_url.setter()),
    |did: String, (id_setter, target_url_setter)| {
      let target_setter = target_url_setter.clone();

      if let Some(did_web) = DidWeb::new(&did) {
        spawn_local(async move {
          if let Some(dsp_endpoint) = get_dsp_endpoint(&did_web).await {
            target_setter.set(dsp_endpoint);
          }
        });
      }

      id_setter.set(did);
    },
  );

  let onchange_target_url = use_callback(target_url.setter(), |value, target_url_setter| {
    target_url_setter.set(value);
  });

  let disabled = false;

  let latest_access_token_context = use_latest_access_token().unwrap();

  let onsubmit = use_callback(
    (
      id.clone(),
      target_url.clone(),
      props.on_create.clone(),
      latest_access_token_context.clone(),
    ),
    |event: SubmitEvent, (id, target_url, on_create, latest_access_token_context)| {
      event.prevent_default();
      let on_create = on_create.clone();
      let latest_access_token_context = latest_access_token_context.clone();

      let id = (**id).clone();
      let target_url = (**target_url).clone();

      spawn_local(async move {
        let server_url = web_sys::window().unwrap().location().origin().unwrap();
        let federated_catalog_management_client = FederatedCatalogClient::new(
          reqwest::Client::new(),
          format!("{server_url}/federated-catalog-management"),
          latest_access_token_context.access_token(),
          FederatedCatalogClientVersion::V4,
        );

        match federated_catalog_management_client
          .create_participant(&FederatedCatalogParticipantCreateForm {
            id: id.clone(),
            name: "".to_string(),
            target_url: target_url.clone(),
          })
          .await
        {
          Ok(()) => {
            on_create.emit(());
          }
          Err(message) => {
            log::error!("{message}");
          }
        }
      })
    },
  );

  let form = html!(
    <Form {onsubmit}>
      <FormGroup label="Counter Party DID" required=true>
        <TextInput required=true value={(*id).to_string()} onchange={onchange_id} />
      </FormGroup>
      <FormGroup label="Counter Party Address" required=true>
        <TextInput
          required=true
          value={(*target_url).to_string()}
          onchange={onchange_target_url}
          r#type={TextInputType::Url}
        />
      </FormGroup>
      <ActionGroup>
        <Button
          variant={ButtonVariant::Primary}
          label="Submit"
          r#type={ButtonType::Submit}
          {disabled}
        />
        <Button variant={ButtonVariant::Secondary} label="Reset" r#type={ButtonType::Reset} />
      </ActionGroup>
    </Form>
  );

  let selected = use_state_eq(|| TabIndex::CounterPartySelect);
  let onselect = use_callback(selected.clone(), |index, selected| selected.set(index));

  let toaster = use_toaster();

  let select_counter_party = use_callback(
    (
      props.on_create.clone(),
      latest_access_token_context.clone(),
      toaster.clone(),
    ),
    |counter_party: CounterParty, (on_create, latest_access_token_context, toaster)| {
      let on_create = on_create.clone();
      let latest_access_token_context = latest_access_token_context.clone();
      let counter_party = counter_party.clone();
      let toaster = toaster.clone();

      spawn_local(async move {
        if let Some(did_web) = DidWeb::new(&counter_party.did) {
          if let Some(dsp_endpoint) = get_dsp_endpoint(&did_web).await {
            let server_url = web_sys::window().unwrap().location().origin().unwrap();
            let federated_catalog_management_client = FederatedCatalogClient::new(
              reqwest::Client::new(),
              format!("{server_url}/federated-catalog-management"),
              latest_access_token_context.access_token(),
              FederatedCatalogClientVersion::V4,
            );

            match federated_catalog_management_client
              .create_participant(&FederatedCatalogParticipantCreateForm {
                id: counter_party.did.clone(),
                name: counter_party.name.to_string(),
                target_url: dsp_endpoint.clone(),
              })
              .await
            {
              Ok(()) => {
                on_create.emit(());
              }
              Err(message) => {
                log::error!("{message}");
                if let Some(toaster) = toaster {
                  toaster.toast(Toast {
                    title: "Unable to create federated catalog participant".to_string(),
                    body: html! { <p>{ message.to_string() }</p> },
                    r#type: AlertType::Warning,
                    ..Default::default()
                  });
                }
              }
            }
          } else {
            if let Some(toaster) = toaster {
              toaster.toast(Toast {
                title: "Unable to create federated catalog participant".to_string(),
                body: html! { <p>{ "No Dataspace Service available for this Participant" }</p> },
                r#type: AlertType::Warning,
                ..Default::default()
              });
            }
          }
        } else {
          if let Some(toaster) = toaster {
            toaster.toast(Toast {
              title: "Unable to create federated catalog participant".to_string(),
              body: html! { <p>{ "Incorrect Participant DID" }</p> },
              r#type: AlertType::Warning,
              ..Default::default()
            });
          }
        }
      })
    },
  );

  if let Some(counter_parties_context) = counter_parties_context
    && !counter_parties_context.counter_parties().is_empty()
  {
    let counter_parties = counter_parties_context
      .counter_parties()
      .iter()
      .map(|counter_party| {
        let onclick = {
          let counter_party = counter_party.clone();

          select_counter_party.reform(move |_| counter_party.clone())
        };

        html!(
          <StackItem>
            <Button variant={ButtonVariant::Secondary} {onclick}>{ &counter_party.name }</Button>
          </StackItem>
        )
      });
    html!(
      <Tabs<TabIndex> {onselect} selected={*selected} r#box=true>
        <Tab<TabIndex> index={TabIndex::CounterPartySelect} title="Select Counter Party">
          <Panel>
            <PanelMain>
              <PanelMainBody>
                <Stack gutter=true>{ for counter_parties }</Stack>
              </PanelMainBody>
            </PanelMain>
          </Panel>
        </Tab<TabIndex>>
        <Tab<TabIndex> index={TabIndex::Form} title="Manual Entry">{ form }</Tab<TabIndex>>
      </Tabs<TabIndex>>
    )
  } else {
    form
  }
}

#[derive(Clone, Copy, PartialEq, Eq)]
enum TabIndex {
  CounterPartySelect,
  Form,
}