mirror of
https://github.com/creating2morrow/neveko.git
synced 2024-12-22 19:49:24 +00:00
testing customer to vendor product ux
This commit is contained in:
parent
a2f8041f59
commit
bbc817d263
8 changed files with 147 additions and 63 deletions
|
@ -175,14 +175,16 @@ pub async fn request_invoice(contact: String) -> Result<reqres::Invoice, Box<dyn
|
|||
}
|
||||
}
|
||||
|
||||
/// Send the request to contact to add them
|
||||
pub async fn add_contact_request(contact: String) -> Result<Contact, Box<dyn Error>> {
|
||||
/// Send the request to contact to add them. Set the prune arg to 1
|
||||
///
|
||||
/// for gpg key removal.
|
||||
pub async fn add_contact_request(contact: String, prune: u32) -> Result<Contact, Box<dyn Error>> {
|
||||
// TODO(c2m): Error handling for http 402 status
|
||||
let host = utils::get_i2p_http_proxy();
|
||||
let proxy = reqwest::Proxy::http(&host)?;
|
||||
let client = reqwest::Client::builder().proxy(proxy).build();
|
||||
match client?
|
||||
.get(format!("http://{}/share", contact))
|
||||
.get(format!("http://{}/share/{}", contact, prune))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
|
|
|
@ -82,6 +82,7 @@ pub fn find_all() -> Vec<Product> {
|
|||
|
||||
/// Modify product
|
||||
pub fn modify(p: Json<Product>) -> Product {
|
||||
// TODO(c2m): don't allow modification to products with un-delivered orders
|
||||
info!("modify product: {}", &p.pid);
|
||||
let f_prod: Product = find(&p.pid);
|
||||
if f_prod.pid == utils::empty_string() {
|
||||
|
@ -143,7 +144,7 @@ pub async fn get_vendor_product(
|
|||
let proxy = reqwest::Proxy::http(&host)?;
|
||||
let client = reqwest::Client::builder().proxy(proxy).build();
|
||||
match client?
|
||||
.get(format!("http://{}/market/product/{}", contact, pid))
|
||||
.get(format!("http://{}/market/{}", contact, pid))
|
||||
.header("proof", jwp)
|
||||
.send()
|
||||
.await
|
||||
|
|
|
@ -29,6 +29,8 @@ pub struct ContactStatus {
|
|||
pub h_exp: String,
|
||||
/// i2p address of current status check
|
||||
pub i2p: String,
|
||||
/// update vendor status of contact
|
||||
pub is_vendor: bool,
|
||||
/// JSON Web Proof of current status check
|
||||
pub jwp: String,
|
||||
/// Alias for contact
|
||||
|
@ -45,6 +47,7 @@ impl Default for ContactStatus {
|
|||
exp: utils::empty_string(),
|
||||
h_exp: utils::empty_string(),
|
||||
i2p: utils::empty_string(),
|
||||
is_vendor: false,
|
||||
jwp: utils::empty_string(),
|
||||
nick: String::from("anon"),
|
||||
signed_key: false,
|
||||
|
|
|
@ -446,7 +446,7 @@ impl eframe::App for AddressBookApp {
|
|||
if ui.button("Add").clicked() {
|
||||
// Get the contacts information from the /share API
|
||||
let contact = self.contact.clone();
|
||||
send_contact_info_req(self.contact_info_tx.clone(), ctx.clone(), contact);
|
||||
send_contact_info_req(self.contact_info_tx.clone(), ctx.clone(), contact, 0);
|
||||
add_contact_timeout(self.contact_timeout_tx.clone(), ctx.clone());
|
||||
self.is_adding = true;
|
||||
}
|
||||
|
@ -548,6 +548,7 @@ impl eframe::App for AddressBookApp {
|
|||
self.contact_info_tx.clone(),
|
||||
ctx.clone(),
|
||||
self.status.i2p.clone(),
|
||||
1,
|
||||
);
|
||||
self.showing_status = true;
|
||||
self.is_pinging = true;
|
||||
|
@ -579,10 +580,15 @@ impl eframe::App for AddressBookApp {
|
|||
|
||||
// Send asyc requests to neveko-core
|
||||
//------------------------------------------------------------------------------
|
||||
fn send_contact_info_req(tx: Sender<models::Contact>, ctx: egui::Context, contact: String) {
|
||||
fn send_contact_info_req(
|
||||
tx: Sender<models::Contact>,
|
||||
ctx: egui::Context,
|
||||
contact: String,
|
||||
prune: u32,
|
||||
) {
|
||||
log::debug!("async send_contact_info_req");
|
||||
tokio::spawn(async move {
|
||||
match contact::add_contact_request(contact).await {
|
||||
match contact::add_contact_request(contact, prune).await {
|
||||
Ok(contact) => {
|
||||
let _ = tx.send(contact);
|
||||
ctx.request_repaint();
|
||||
|
|
|
@ -7,11 +7,14 @@ use std::sync::mpsc::{
|
|||
pub struct MarketApp {
|
||||
contact_info_tx: Sender<models::Contact>,
|
||||
contact_info_rx: Receiver<models::Contact>,
|
||||
contact_timeout_tx: Sender<bool>,
|
||||
contact_timeout_rx: Receiver<bool>,
|
||||
find_vendor: String,
|
||||
get_vendor_products_tx: Sender<Vec<models::Product>>,
|
||||
get_vendor_products_rx: Receiver<Vec<models::Product>>,
|
||||
get_vendor_product_tx: Sender<models::Product>,
|
||||
get_vendor_product_rx: Receiver<models::Product>,
|
||||
is_loading: bool,
|
||||
is_ordering: bool,
|
||||
is_pinging: bool,
|
||||
is_product_image_set: bool,
|
||||
|
@ -21,6 +24,7 @@ pub struct MarketApp {
|
|||
is_showing_orders: bool,
|
||||
is_showing_vendor_status: bool,
|
||||
is_showing_vendors: bool,
|
||||
is_timeout: bool,
|
||||
is_vendor_enabled: bool,
|
||||
is_window_shopping: bool,
|
||||
orders: Vec<models::Order>,
|
||||
|
@ -36,13 +40,13 @@ pub struct MarketApp {
|
|||
_refresh_on_delete_product_tx: Sender<bool>,
|
||||
_refresh_on_delete_product_rx: Receiver<bool>,
|
||||
s_contact: models::Contact,
|
||||
showing_vendor_status: bool,
|
||||
vendor_status: utils::ContactStatus,
|
||||
vendors: Vec<models::Contact>,
|
||||
}
|
||||
|
||||
impl Default for MarketApp {
|
||||
fn default() -> Self {
|
||||
let (contact_timeout_tx, contact_timeout_rx) = std::sync::mpsc::channel();
|
||||
let (_refresh_on_delete_product_tx, _refresh_on_delete_product_rx) =
|
||||
std::sync::mpsc::channel();
|
||||
let read_product_image = std::fs::read("./assets/qr.png").unwrap_or(Vec::new());
|
||||
|
@ -55,11 +59,14 @@ impl Default for MarketApp {
|
|||
MarketApp {
|
||||
contact_info_rx,
|
||||
contact_info_tx,
|
||||
contact_timeout_rx,
|
||||
contact_timeout_tx,
|
||||
find_vendor: utils::empty_string(),
|
||||
get_vendor_products_rx,
|
||||
get_vendor_products_tx,
|
||||
get_vendor_product_rx,
|
||||
get_vendor_product_tx,
|
||||
is_loading: false,
|
||||
is_ordering: false,
|
||||
is_pinging: false,
|
||||
is_product_image_set: false,
|
||||
|
@ -69,6 +76,7 @@ impl Default for MarketApp {
|
|||
is_showing_product_update: false,
|
||||
is_showing_vendor_status: false,
|
||||
is_showing_vendors: false,
|
||||
is_timeout: false,
|
||||
is_vendor_enabled,
|
||||
is_window_shopping: false,
|
||||
orders: Vec::new(),
|
||||
|
@ -88,7 +96,6 @@ impl Default for MarketApp {
|
|||
_refresh_on_delete_product_tx,
|
||||
_refresh_on_delete_product_rx,
|
||||
s_contact: Default::default(),
|
||||
showing_vendor_status: false,
|
||||
vendor_status: Default::default(),
|
||||
vendors: Vec::new(),
|
||||
}
|
||||
|
@ -99,17 +106,54 @@ impl eframe::App for MarketApp {
|
|||
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
|
||||
// Hook into async channel threads
|
||||
//-----------------------------------------------------------------------------------
|
||||
|
||||
if let Ok(contact_info) = self.contact_info_rx.try_recv() {
|
||||
self.s_contact = contact_info;
|
||||
if self.s_contact.xmr_address != utils::empty_string() {
|
||||
self.is_pinging = false;
|
||||
self.vendor_status.is_vendor = self.s_contact.is_vendor;
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(vendor_products) = self.get_vendor_products_rx.try_recv() {
|
||||
self.is_loading = false;
|
||||
self.products = vendor_products;
|
||||
}
|
||||
|
||||
if let Ok(vendor_product) = self.get_vendor_product_rx.try_recv() {
|
||||
self.is_loading = false;
|
||||
if !vendor_product.image.is_empty() {
|
||||
// only pull image from vendor when we want to view
|
||||
let file_path = format!(
|
||||
"/home/{}/.neveko/{}.jpeg",
|
||||
std::env::var("USER").unwrap_or(String::from("user")),
|
||||
vendor_product.pid
|
||||
);
|
||||
if self.is_window_shopping {
|
||||
self.is_loading = true;
|
||||
let contents = std::fs::read(&file_path).unwrap_or(Vec::new());
|
||||
// this image should uwrap if vendor image bytes are
|
||||
// bad
|
||||
let default_img = std::fs::read("./assets/qr.png").unwrap_or(Vec::new());
|
||||
let default_r_img =
|
||||
egui_extras::RetainedImage::from_image_bytes("qr.png", &default_img)
|
||||
.unwrap();
|
||||
self.product_image =
|
||||
egui_extras::RetainedImage::from_image_bytes(file_path, &contents)
|
||||
.unwrap_or(default_r_img);
|
||||
}
|
||||
}
|
||||
self.product_from_vendor = vendor_product;
|
||||
self.is_product_image_set = true;
|
||||
self.is_showing_product_image = true;
|
||||
self.is_loading = false;
|
||||
}
|
||||
|
||||
if let Ok(timeout) = self.contact_timeout_rx.try_recv() {
|
||||
self.is_timeout = true;
|
||||
if timeout {
|
||||
self.is_pinging = false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(c2m): create order form
|
||||
|
@ -163,7 +207,7 @@ impl eframe::App for MarketApp {
|
|||
})
|
||||
.body(|mut body| {
|
||||
for v in &self.vendors {
|
||||
if v.i2p_address.contains(&self.find_vendor) && v.is_vendor {
|
||||
if v.i2p_address.contains(&self.find_vendor) {
|
||||
let row_height = 20.0;
|
||||
body.row(row_height, |mut row| {
|
||||
row.col(|ui| {
|
||||
|
@ -195,6 +239,7 @@ impl eframe::App for MarketApp {
|
|||
String::from("gui-jwp"),
|
||||
String::from(&v.i2p_address),
|
||||
);
|
||||
log::debug!("jwp: {}", self.vendor_status.jwp);
|
||||
let r_exp = utils::search_gui_db(
|
||||
String::from("gui-exp"),
|
||||
String::from(&v.i2p_address),
|
||||
|
@ -220,7 +265,11 @@ impl eframe::App for MarketApp {
|
|||
ctx.clone(),
|
||||
self.vendor_status.i2p.clone(),
|
||||
);
|
||||
self.showing_vendor_status = true;
|
||||
vendor_status_timeout(
|
||||
self.contact_timeout_tx.clone(),
|
||||
ctx.clone(),
|
||||
);
|
||||
self.is_showing_vendor_status = true;
|
||||
self.is_pinging = true;
|
||||
}
|
||||
});
|
||||
|
@ -234,8 +283,10 @@ impl eframe::App for MarketApp {
|
|||
&& self.vendor_status.signed_key
|
||||
&& self.vendor_status.jwp != utils::empty_string()
|
||||
&& v.i2p_address == self.vendor_status.i2p
|
||||
&& self.vendor_status.is_vendor
|
||||
{
|
||||
if ui.button("View Products").clicked() {
|
||||
self.is_loading = true;
|
||||
send_products_from_vendor_req(
|
||||
self.get_vendor_products_tx.clone(),
|
||||
ctx.clone(),
|
||||
|
@ -244,6 +295,7 @@ impl eframe::App for MarketApp {
|
|||
);
|
||||
self.is_window_shopping = true;
|
||||
self.is_showing_products = true;
|
||||
self.is_showing_vendors = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -274,7 +326,13 @@ impl eframe::App for MarketApp {
|
|||
} else {
|
||||
"offline"
|
||||
};
|
||||
let mode = if self.vendor_status.is_vendor {
|
||||
"enabled "
|
||||
} else {
|
||||
"disabled"
|
||||
};
|
||||
ui.label(format!("status: {}", status));
|
||||
ui.label(format!("vendor mode: {}", mode));
|
||||
ui.label(format!("nick: {}", self.vendor_status.nick));
|
||||
ui.label(format!("tx proof: {}", self.vendor_status.txp));
|
||||
ui.label(format!("jwp: {}", self.vendor_status.jwp));
|
||||
|
@ -314,7 +372,10 @@ impl eframe::App for MarketApp {
|
|||
Column,
|
||||
TableBuilder,
|
||||
};
|
||||
|
||||
if self.is_loading {
|
||||
ui.add(egui::Spinner::new());
|
||||
ui.label("loading...");
|
||||
}
|
||||
let table = TableBuilder::new(ui)
|
||||
.striped(true)
|
||||
.resizable(true)
|
||||
|
@ -367,7 +428,6 @@ impl eframe::App for MarketApp {
|
|||
row.col(|ui| {
|
||||
if ui.button("View").clicked() {
|
||||
if !self.is_product_image_set {
|
||||
self.is_showing_product_image = true;
|
||||
let file_path = format!(
|
||||
"/home/{}/.neveko/{}.jpeg",
|
||||
std::env::var("USER")
|
||||
|
@ -376,9 +436,8 @@ impl eframe::App for MarketApp {
|
|||
);
|
||||
// For the sake of brevity product list doesn't have
|
||||
// image bytes, get them
|
||||
let mut i_product = product::find(&p.pid);
|
||||
// only pull image from vendor when we want to view
|
||||
if self.is_window_shopping {
|
||||
self.is_loading = true;
|
||||
send_product_from_vendor_req(
|
||||
self.get_vendor_product_tx.clone(),
|
||||
ctx.clone(),
|
||||
|
@ -386,32 +445,14 @@ impl eframe::App for MarketApp {
|
|||
self.vendor_status.jwp.clone(),
|
||||
String::from(&p.pid),
|
||||
);
|
||||
let e_product: models::Product = models::Product {
|
||||
pid: self.product_from_vendor.pid.clone(),
|
||||
description: self
|
||||
.product_from_vendor
|
||||
.description
|
||||
.clone(),
|
||||
image: self
|
||||
.product_from_vendor
|
||||
.image
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect(),
|
||||
in_stock: self.product_from_vendor.in_stock,
|
||||
name: self.product_from_vendor.name.clone(),
|
||||
price: self.product_from_vendor.price,
|
||||
qty: self.product_from_vendor.qty,
|
||||
};
|
||||
i_product = e_product;
|
||||
}
|
||||
} else {
|
||||
let i_product = product::find(&p.pid);
|
||||
match std::fs::write(&file_path, &i_product.image) {
|
||||
Ok(w) => w,
|
||||
Err(_) => {
|
||||
log::error!("failed to write product image")
|
||||
}
|
||||
};
|
||||
self.is_product_image_set = true;
|
||||
let contents =
|
||||
std::fs::read(&file_path).unwrap_or(Vec::new());
|
||||
if !i_product.image.is_empty() {
|
||||
|
@ -431,7 +472,11 @@ impl eframe::App for MarketApp {
|
|||
)
|
||||
.unwrap_or(default_r_img);
|
||||
}
|
||||
}
|
||||
if !self.is_window_shopping {
|
||||
self.is_product_image_set = true;
|
||||
self.is_showing_product_image = true;
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
|
@ -633,6 +678,8 @@ impl eframe::App for MarketApp {
|
|||
}
|
||||
});
|
||||
if ui.button("View Vendors").clicked() {
|
||||
// assume all contacts are vendors until updated status check
|
||||
self.vendors = contact::find_all();
|
||||
self.is_showing_vendors = true;
|
||||
}
|
||||
ui.label("\n");
|
||||
|
@ -704,6 +751,7 @@ impl eframe::App for MarketApp {
|
|||
if ui.button("View Products").clicked() {
|
||||
self.products = product::find_all();
|
||||
self.is_showing_products = true;
|
||||
self.is_showing_vendors = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -723,7 +771,7 @@ fn _refresh_on_delete_product_req(_tx: Sender<bool>, _ctx: egui::Context) {
|
|||
fn send_contact_info_req(tx: Sender<models::Contact>, ctx: egui::Context, contact: String) {
|
||||
log::debug!("async send_contact_info_req");
|
||||
tokio::spawn(async move {
|
||||
match contact::add_contact_request(contact).await {
|
||||
match contact::add_contact_request(contact, 1).await {
|
||||
Ok(contact) => {
|
||||
let _ = tx.send(contact);
|
||||
ctx.request_repaint();
|
||||
|
@ -774,3 +822,15 @@ fn send_product_from_vendor_req(
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn vendor_status_timeout(tx: Sender<bool>, ctx: egui::Context) {
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(
|
||||
crate::ADD_CONTACT_TIMEOUT_SECS,
|
||||
))
|
||||
.await;
|
||||
log::error!("vendor status timeout");
|
||||
let _ = tx.send(true);
|
||||
ctx.request_repaint();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -36,12 +36,23 @@ pub async fn get_i2p_status() -> Custom<Json<i2p::HttpProxyStatus>> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Share your contact information
|
||||
/// Share your contact information.
|
||||
///
|
||||
/// 0 - returns full info with gpg key
|
||||
///
|
||||
/// 1 - return pruned info without gpg key
|
||||
///
|
||||
/// Protected: false
|
||||
#[get("/")]
|
||||
pub async fn share_contact_info() -> Custom<Json<models::Contact>> {
|
||||
#[get("/<pruned>")]
|
||||
pub async fn share_contact_info(pruned: u32) -> Custom<Json<models::Contact>> {
|
||||
let info: models::Contact = contact::share().await;
|
||||
if pruned == 1 {
|
||||
let p_info: models::Contact = models::Contact {
|
||||
gpg_key: Vec::new(),
|
||||
..info
|
||||
};
|
||||
return Custom(Status::Ok, Json(p_info));
|
||||
}
|
||||
Custom(Status::Ok, Json(info))
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ async fn rocket() -> _ {
|
|||
routes![
|
||||
controller::create_order,
|
||||
controller::create_dispute,
|
||||
controller::get_product,
|
||||
controller::get_products,
|
||||
controller::finalize_order,
|
||||
controller::request_shipment,
|
||||
|
|
Loading…
Reference in a new issue