edc_web_components/pages/
offer_page.rs1use crate::components::{ListAssetsGallery, SelectedFederatedCatalogOffer};
2use crate::contexts::{use_edc_connector_context, use_edc_identity_hub_context};
3use crate::models::{AssetItem, DatasetExtraFields};
4use crate::services::DidResolver;
5use edc_connector_client::EdcConnectorApiVersion;
6use edc_connector_client::types::Protocol;
7use edc_connector_client::types::catalog::CatalogRequest;
8use edc_connector_client::types::query::Query;
9use edc_identity_hub_client::models::{DidWeb, IdentityServiceType};
10use patternfly_yew::prelude::*;
11use yew::prelude::*;
12use yew::suspense::use_future_with;
13
14#[derive(Clone, Debug, PartialEq, Properties)]
15pub struct OfferPageProps {
16 pub onselectedoffer: Callback<SelectedFederatedCatalogOffer>,
17 pub on_new_asset: Callback<()>,
18 pub on_new_policy: Callback<()>,
19 pub on_new_contract: Callback<()>,
20}
21
22#[component]
23pub fn OfferPage(props: &OfferPageProps) -> Html {
24 let refresh = use_state(|| 0usize);
25 let offset = use_state(|| 0usize);
26 let limit = use_state(|| 10usize);
27
28 let on_offset = use_callback(
29 (refresh.clone(), offset.setter()),
30 |offset, (refresh, offset_setter)| {
31 offset_setter.set(offset);
32 refresh.set(**refresh + 1);
33 },
34 );
35
36 let on_limit = use_callback(
37 (refresh.clone(), limit.setter()),
38 |limit, (refresh, limit_setter)| {
39 limit_setter.set(limit);
40 refresh.set(**refresh + 1);
41 },
42 );
43
44 let fallback = html! {
45 <Bullseye>
46 <Spinner size={SpinnerSize::Lg} />
47 </Bullseye>
48 };
49
50 html!(
51 <Stack gutter=true>
52 <StackItem>
53 <Split gutter=true>
54 <SplitItem fill=true>
55 <Title level={Level::H3} size={Size::XXLarge}>{ "List Offers" }</Title>
56 </SplitItem>
57 </Split>
58 </StackItem>
59 <StackItem>
60 <Suspense {fallback}>
61 <OfferPageInner
62 offset={*offset}
63 limit={*limit}
64 {on_offset}
65 {on_limit}
66 force_refresh={*refresh}
67 onselectedoffer={props.onselectedoffer.clone()}
68 on_new_asset={props.on_new_asset.clone()}
69 on_new_policy={props.on_new_policy.clone()}
70 on_new_contract={props.on_new_contract.clone()}
71 />
72 </Suspense>
73 </StackItem>
74 </Stack>
75 )
76}
77
78#[derive(Clone, Debug, PartialEq, Properties)]
79pub struct OfferPageInnerProps {
80 pub offset: usize,
81 pub limit: usize,
82 pub on_offset: Callback<usize>,
83 pub on_limit: Callback<usize>,
84 pub force_refresh: usize,
85 pub onselectedoffer: Callback<SelectedFederatedCatalogOffer>,
86 pub on_new_asset: Callback<()>,
87 pub on_new_policy: Callback<()>,
88 pub on_new_contract: Callback<()>,
89}
90
91#[component]
92pub fn OfferPageInner(props: &OfferPageInnerProps) -> HtmlResult {
93 let edc_connector_context = use_edc_connector_context();
94 let edc_identity_hub_context = use_edc_identity_hub_context();
95
96 let asset_items = use_future_with(
97 (
98 edc_identity_hub_context,
99 edc_connector_context,
100 props.limit,
101 props.offset,
102 props.force_refresh,
103 ),
104 |parameters| async move {
105 let (edc_identity_hub_context, edc_connector_context, limit, offset, _) =
106 (*parameters).clone();
107
108 if let Some(did_web) = DidWeb::new(edc_identity_hub_context.participant_did())
109 && let Ok(did_data) = DidResolver::new(reqwest::Client::new())
110 .resolve(did_web)
111 .await
112 && let Some(identity_service) = did_data
113 .get_identity_services(IdentityServiceType::DataService)
114 .first()
115 && let Some(dataspace_service_client) = identity_service.get_dataspace_service_client()
116 && let Some(dsp_endpoint) = dataspace_service_client.get_first_service_endpoint().await
117 && let Some(client) = edc_connector_context.get_client()
118 {
119 let request = CatalogRequest::builder()
120 .counter_party_address(dsp_endpoint.clone())
121 .counter_party_id(edc_identity_hub_context.participant_did())
122 .protocol(Protocol::default())
123 .query_spec(
124 Query::builder()
125 .limit(limit as u32)
126 .offset(offset as u32)
127 .build(),
128 )
129 .build();
130
131 if let Ok(response) = client
132 .catalogue(EdcConnectorApiVersion::V4)
133 .request::<DatasetExtraFields>(&request)
134 .await
135 {
136 response
137 .datasets()
138 .iter()
139 .map(|dataset| {
140 let dataset_id = dataset.id().to_string();
141 let asset_item = AssetItem::from(dataset);
142
143 let selected_offer = SelectedFederatedCatalogOffer {
144 originator: dsp_endpoint.clone(),
145 provider_id: edc_identity_hub_context.participant_did().to_string(),
146 dataset_id,
147 };
148
149 (asset_item, selected_offer)
150 })
151 .collect::<Vec<_>>()
152 .into_iter()
153 .unzip()
154 } else {
155 (Vec::new(), Vec::new())
156 }
157 } else {
158 (Vec::new(), Vec::new())
159 }
160 },
161 )?;
162
163 let (asset_items, selected_offers): (Vec<AssetItem>, Vec<SelectedFederatedCatalogOffer>) =
164 (*asset_items).clone();
165
166 let onshow = use_callback(
167 (props.onselectedoffer.clone(), selected_offers),
168 |dataset_id, (onselectedoffer, selected_offers)| {
169 if let Some(selected_offer) = selected_offers
170 .iter()
171 .find(|selected_offer| selected_offer.dataset_id == dataset_id)
172 {
173 onselectedoffer.emit(selected_offer.clone());
174 }
175 },
176 );
177
178 if asset_items.is_empty() {
179 let primary = { Action::new("Create my first asset", props.on_new_asset.reform(|_| ())) };
180
181 let secondaries = vec![
182 Action::new("Create my first policy", props.on_new_policy.reform(|_| ())),
183 Action::new(
184 "Create my first contract",
185 props.on_new_contract.reform(|_| ()),
186 ),
187 ];
188
189 Ok(html! {
190 <EmptyState title="Empty state" icon={Icon::Cubes} {primary} {secondaries}>
191 <div>
192 <p>{ "There is no offer yet." }</p>
193 <p>
194 { "The first step consists of creating an asset, then a policy, and finally a contract definition." }
195 </p>
196 <p>{ "This results in an offer." }</p>
197 </div>
198 </EmptyState>
199 })
200 } else {
201 Ok(html!(<ListAssetsGallery {asset_items} {onshow} />))
202 }
203}