1use dioxus::prelude::*;
8
9#[component]
15pub fn History(name: String) -> Element {
16 let name = std::rc::Rc::new(name);
17 let name_for_future = name.clone();
18
19 let transactions_resource = use_resource(move || {
20 let name_clone = name_for_future.clone();
21 async move { get_transactions_for_user_server(name_clone.to_string()).await }
22 });
23
24 rsx! {
25 div { id: "history-page",
26 match &*transactions_resource.read() {
27 None => rsx! {
28 p { "Loading history..." }
29 },
30 Some(Ok(transactions)) => {
31 if transactions.is_empty() {
32 rsx! {
33 p { "No transactions found for {name}." }
34 }
35 } else {
36 rsx! {
37 ul { class: "transactions-list",
38 for transaction in transactions.iter() {
39 li {
40 key: "{transaction.lamport_time}-{transaction.source_node}",
41 class: "transaction-card",
42 p {
43 strong { "From:" }
44 " {transaction.from_user}"
45 }
46 p {
47 strong { "To:" }
48 " {transaction.to_user}"
49 }
50 p {
51 strong { "Amount:" }
52 " {transaction.amount:.2}"
53 }
54 if let Some(msg) = &transaction.optional_msg {
55 if !msg.is_empty() {
56 p {
57 strong { "Message:" }
58 " {msg}"
59 }
60 }
61 }
62 }
63 }
64 }
65 }
66 }
67 }
68 Some(Err(e)) => rsx! {
69 p { class: "error-message", "Error loading history: {e}" }
70 },
71 }
72 }
73 }
74}
75
76#[component]
82pub fn Withdraw(name: String) -> Element {
83 let mut withdraw_amount = use_signal(|| 0f64);
84 let name = std::rc::Rc::new(name);
85
86 let mut error_signal = use_signal(|| None::<String>);
87
88 let name_for_future = name.clone();
89
90 rsx! {
91 div { id: "withdraw-form",
92 form {
93 label { r#for: "fwithdraw", "Withdraw amount :" }
94 input {
95 r#type: "number",
96 id: "form-withdraw",
97 r#name: "fwithdraw",
98 step: 0.01,
99 value: "{withdraw_amount}",
100 oninput: move |event| {
101 if let Ok(as_number) = event.value().parse::<f64>() {
102 withdraw_amount.set(as_number);
103 }
104 },
105 }
106 button {
107 r#type: "submit",
108 onclick: move |_| {
109 let name = name_for_future.clone();
110 let amount = *withdraw_amount.read();
111 async move {
112 if amount >= 0.0 {
113 if let Ok(_) = withdraw_for_user_server(name.to_string(), amount).await {
114 withdraw_amount.set(0.0);
115 error_signal.set(None);
116 }
117 } else {
118 error_signal
119 .set(
120 Some(
121 format!("Please enter a positive amount, you gave {amount}."),
122 ),
123 );
124 }
125 }
126 },
127 "Submit"
128 }
129 }
130 if let Some(error) = &*error_signal.read() {
131 p { class: "error-message", "{error}" }
132 }
133 }
134 }
135}
136
137const COCA_IMG: Asset = asset!("/assets/images/coca.png");
138const CHIPS_IMG: Asset = asset!("/assets/images/chips.png");
139const SANDWICH_IMG: Asset = asset!("/assets/images/sandwich.png");
140const COFFEE_IMG: Asset = asset!("/assets/images/coffee.png");
141
142const PRODUCTS: &[(&str, f64, Asset)] = &[
143 ("Coca", 1.50, COCA_IMG),
144 ("Chips", 2.00, CHIPS_IMG),
145 ("Sandwich", 4.50, SANDWICH_IMG),
146 ("Coffee", 1.20, COFFEE_IMG),
147];
148
149#[component]
156pub fn Pay(name: String) -> Element {
157 let mut product_quantities = use_signal(|| vec![0u32; PRODUCTS.len()]);
158 let name_for_payment = std::rc::Rc::new(name.clone());
159
160 let mut error_signal = use_signal(|| None::<String>);
161
162 let handle_pay = move |_| {
163 let current_quantities = product_quantities.read().clone();
164 let name_clone = name_for_payment.clone();
165
166 let mut total_amount = 0.0;
167 for (i, &(_, price, _)) in PRODUCTS.iter().enumerate() {
168 if let Some(&quantity) = current_quantities.get(i) {
169 total_amount += price * quantity as f64;
170 }
171 }
172
173 spawn(async move {
174 if total_amount > 0.0 {
175 if let Ok(_) = pay_for_user_server(name_clone.to_string(), total_amount).await {
176 log::info!("Payment successful.");
177 product_quantities.set(vec![0u32; PRODUCTS.len()]);
178 error_signal.set(None);
179 }
180 } else {
181 log::warn!("Attempted to pay with a total of 0.0. No action taken.");
182 error_signal.set(Some(
183 "Cannot pay €0. Please select at least one item.".to_string(),
184 ));
185 }
186 });
187 };
188
189 let current_total_display = use_memo(move || {
190 let mut total = 0.0;
191 let quantities_read = product_quantities.read();
192 for (i, &(_, price, _)) in PRODUCTS.iter().enumerate() {
193 if let Some(&quantity) = quantities_read.get(i) {
194 total += price * quantity as f64;
195 }
196 }
197 total
198 });
199
200 rsx! {
201 div { id: "pay-page",
202 div {
203 for (index , (product_name , price , image_path)) in PRODUCTS.iter().enumerate() {
204 div { key: "{product_name}-{index}",
205 img { src: "{image_path}", alt: "{product_name}" }
206 div { class: "product-info",
207 h3 { "{product_name}" }
208 p { "€{price:.2}" }
209 div {
210 label { r#for: "qty-{index}", "Quantity:" }
211 input {
212 r#type: "number",
213 id: "qty-{index}",
214 min: "0",
215 value: "{product_quantities.read()[index]}",
216 oninput: move |event| {
217 let mut pq_signal_for_input = product_quantities;
218 if let Ok(new_quantity) = event.value().parse::<u32>() {
219 let mut quantities_writer = pq_signal_for_input.write();
220 if index < quantities_writer.len() {
221 quantities_writer[index] = new_quantity;
222 }
223 } else if event.value().is_empty() {
224 let mut quantities_writer = pq_signal_for_input.write();
225 if index < quantities_writer.len() {
226 quantities_writer[index] = 0;
227 }
228 }
229 },
230 }
231 }
232 }
233 }
234 }
235 }
236
237 div { class: "cart-summary",
238 h2 { "Order Summary" }
239 h3 { "Total: €{current_total_display():.2}" }
240 form {
241 button {
242 r#type: "submit",
243 disabled: current_total_display() == 0.0,
244 onclick: handle_pay,
245 "Pay Now"
246 }
247 }
248 }
249
250 if let Some(error) = &*error_signal.read() {
251 p { class: "error-message", "{error}" }
252 }
253 }
254 }
255}
256
257#[component]
265pub fn Refund(name: String) -> Element {
266 let name = std::rc::Rc::new(name);
267 let name_for_future = name.clone();
268
269 let mut error_signal = use_signal(|| None::<String>);
270
271 let transactions_resource = use_resource(move || {
272 let name_clone = name_for_future.clone();
273 async move { get_transactions_for_user_server(name_clone.to_string()).await }
274 });
275
276 rsx! {
277 div { id: "refund-page",
278 match &*transactions_resource.read() {
279 None => rsx! {
280 p { "Loading history..." }
281 },
282 Some(Ok(transactions)) => {
283 let name_clone = name.clone();
284 if transactions.is_empty() {
285 rsx! {
286 p { "No transactions found for {name_clone}." }
287 }
288 } else {
289 rsx! {
290 ul { class: "transactions-list",
291 for transaction in transactions.iter() {
292 li {
293 key: "{transaction.lamport_time}-{transaction.source_node}",
294 class: "transaction-card",
295 p {
296 strong { "From:" }
297 " {transaction.from_user}"
298 }
299 p {
300 strong { "To:" }
301 " {transaction.to_user}"
302 }
303 p {
304 strong { "Amount:" }
305 " {transaction.amount:.2}"
306 }
307 if let Some(msg) = &transaction.optional_msg {
308 if !msg.is_empty() {
309 p {
310 strong { "Message:" }
311 " {msg}"
312 }
313 }
314 }
315 {
316 let transaction_for_refund = transaction.clone();
317 let name_for_refund = name.clone();
318 let mut resource_to_refresh = transactions_resource.clone();
319 rsx! {
320 button {
321 r#type: "submit",
322 onclick: move |_| {
323 let name_for_future = name_for_refund.clone();
324 let transaction_for_future = transaction_for_refund.clone();
325 async move {
326 if let Ok(_) = refund_transaction_server(
327 name_for_future.to_string(),
328 transaction_for_future.lamport_time,
329 transaction_for_future.source_node,
330 )
331 .await
332 {
333 if let Ok(_) = get_transactions_for_user_server(
334 name_for_future.to_string(),
335 )
336 .await
337 {
338 error_signal.set(None);
339 resource_to_refresh.restart();
340 }
341 }
342 }
343 },
344 "Refund"
345 }
346 }
347 }
348 }
349 }
350 }
351 if let Some(error) = &*error_signal.read() {
352 p { class: "error-message", "{error}" }
353 }
354 }
355 }
356 }
357 Some(Err(e)) => rsx! {
358 p { class: "error-message", "Error loading transactions: {e}" }
359 },
360 }
361 }
362 }
363}
364
365#[component]
376pub fn Transfer(name: String) -> Element {
377 let mut transfer_amount = use_signal(|| 0f64);
378 let mut transfer_message = use_signal(String::new);
379 let mut selected_user = use_signal(String::new);
380 let name = std::rc::Rc::new(name);
381 let name_for_future = name.clone();
382
383 let mut error_signal = use_signal(|| None::<String>);
384
385 let users_resource = use_resource({
386 move || {
387 let current_user = name_for_future.clone();
388 async move {
389 let all_users = get_users_server().await.unwrap_or_default();
390 all_users
391 .into_iter()
392 .filter(|u| u != current_user.as_ref())
393 .collect::<Vec<_>>()
394 }
395 }
396 });
397
398 rsx! {
399 div { id: "transfer-page",
400 match &*users_resource.read() {
401 None => rsx! {
402 p { "Loading users..." }
403 },
404 Some(users) => rsx! {
405 form {
406 label { r#for: "user-select", "Select user to transfer to:" }
407 select {
408 id: "user-select",
409 onchange: move |evt| {
410 selected_user.set(evt.value());
411 },
412 option {
413 value: "",
414 disabled: true,
415 selected: selected_user.read().is_empty(),
416 "Choose a user"
417 }
418 for user in users {
419 option { key: "{user}", value: "{user}", "{user}" }
420 }
421 }
422 label { r#for: "transfer-amount", "Amount to transfer:" }
423 input {
424 r#type: "number",
425 id: "transfer-amount",
426 step: 0.01,
427 value: "{transfer_amount}",
428 oninput: move |evt| {
429 if let Ok(val) = evt.value().parse::<f64>() {
430 transfer_amount.set(val);
431 }
432 },
433 }
434 label { r#for: "transfer-message", "Message (optional):" }
435 input {
436 r#type: "text",
437 id: "transfer-message",
438 value: "{transfer_message}",
439 oninput: move |evt| {
440 transfer_message.set(evt.value());
441 },
442 }
443 button {
444 r#type: "button",
445 onclick: move |_| {
446 async move {
447 if let Ok(message) = get_random_message_server().await {
448 transfer_message.set(message);
449 }
450 }
451 },
452 "Select a random message"
453 }
454 button {
455 r#type: "submit",
456 onclick: move |_| {
457 let to_user = selected_user.read().clone();
458 let amount = *transfer_amount.read();
459 let message = transfer_message.read().clone();
460 let from_user = name.clone();
461 async move {
462 if !to_user.is_empty() && amount > 0.0 {
463 if let Ok(_) = transfer_from_user_to_user_server(
464 from_user.to_string(),
465 to_user,
466 amount,
467 message,
468 )
469 .await
470 {
471 transfer_amount.set(0.0);
472 transfer_message.set(String::new());
473 selected_user.set(String::new());
474 error_signal.set(None);
475 }
476 } else {
477 error_signal
478 .set(
479 Some(
480 "Please select a user and enter a positive amount."
481 .to_string(),
482 ),
483 );
484 }
485 }
486 },
487 "Transfer"
488 }
489 }
490 },
491 }
492 if let Some(error) = &*error_signal.read() {
493 p { class: "error-message", "{error}" }
494 }
495 }
496 }
497}
498
499#[component]
505pub fn Deposit(name: String) -> Element {
506 let mut deposit_amount = use_signal(|| 0f64);
507 let name = std::rc::Rc::new(name);
508
509 let mut error_signal = use_signal(|| None::<String>);
510
511 let name_for_future = name.clone();
512
513 rsx! {
514 div { id: "deposit-form",
515 form {
516 label { r#for: "fdeposit", "Deposit amount :" }
517 input {
518 r#type: "number",
519 id: "form-deposit",
520 r#name: "fdeposit",
521 step: 0.01,
522 value: "{deposit_amount}",
523 oninput: move |event| {
524 if let Ok(as_number) = event.value().parse::<f64>() {
525 deposit_amount.set(as_number);
526 }
527 },
528 }
529 button {
530 r#type: "submit",
531 onclick: move |_| {
532 let name = name_for_future.clone();
533 let amount = *deposit_amount.read();
534 async move {
535 if amount >= 0.0 {
536 if let Ok(_) = deposit_for_user_server(name.to_string(), amount).await {
537 deposit_amount.set(0.0);
538 error_signal.set(None);
539 }
540 } else {
541 error_signal
542 .set(
543 Some(
544 format!("Please enter a positive amount, you gave {amount}."),
545 ),
546 );
547 }
548 }
549 },
550 "Submit"
551 }
552 }
553 if let Some(error) = &*error_signal.read() {
554 p { class: "error-message", "{error}" }
555 }
556 }
557 }
558}
559
560#[cfg(feature = "server")]
561const RANDOM_MESSAGE: &[&str] = &[
562 "Prend tes 200 balles et va te payer des cours de theatre",
563 "C'est pour toi bb",
564 "Love sur toi",
565 "Phrase non aléatoire",
566 "Votre argent messire",
567 "Acompte sur livraison cocaine",
568 "Votre argent seigneur",
569 "Pour tout ce que tu fais pour moi",
570 "Remboursement horny.com",
571 "Puta, où tu étais quand j'mettais des sept euros d'essence",
572 "Parce que l'argent n'est pas un problème pour moi",
573 "Tiens le rat",
574 "Pour le rein",
575 "Abonnement OnlyFans",
576 "Pour notre dernière nuit, pourboire non compris",
577 "ça fait beaucoup la non ?",
578 "Chantage SexTape",
579 "Argent sale",
580 "Adhésion front national",
581 "Ce que tu sais...",
582 "Remboursement dot de ta soeur",
583 "Rien à ajouter",
584 "Téléphone rose",
585 "Raison : \"GnaGnaGna moi je paye pas pour vous\"",
586 "Fond de tiroir",
587 "Epilation des zones intimes",
588 "Pour m'avoir gratouillé le dos",
589 "La reine Babeth vous offre cet argent",
590 "Nan t'inquiete",
591];
592
593#[cfg(feature = "server")]
594fn get_seed() -> u64 {
595 use std::time::{SystemTime, UNIX_EPOCH};
596 SystemTime::now()
597 .duration_since(UNIX_EPOCH)
598 .unwrap()
599 .as_nanos() as u64
600}
601
602#[cfg(feature = "server")]
603fn lcg(seed: u64) -> u64 {
604 const A: u64 = 6364136223846793005;
605 const C: u64 = 1;
606 seed.wrapping_mul(A).wrapping_add(C)
607}
608
609#[server]
610async fn get_random_message_server() -> Result<String, ServerFnError> {
611 let seed = get_seed();
612 let random_number = lcg(seed);
613 let message = RANDOM_MESSAGE[random_number as usize % RANDOM_MESSAGE.len()];
614 Ok(message.to_string())
615}
616
617#[server]
618async fn get_users_server() -> Result<Vec<String>, ServerFnError> {
619 use crate::db;
620 let users = db::get_users()?;
621 Ok(users)
622}
623
624#[server]
625async fn deposit_for_user_server(user: String, amount: f64) -> Result<(), ServerFnError> {
626 if amount < 0.0 {
627 return Err(ServerFnError::new("Amount cannot be negative."));
628 }
629
630 if let Err(e) = crate::control::enqueue_critical(crate::control::CriticalCommands::Deposit {
631 name: user,
632 amount: amount,
633 })
634 .await
635 {
636 return Err(ServerFnError::new(format!(
637 "[SERVER] Failed to diffuse deposit : {e}"
638 )));
639 }
640
641 Ok(())
642}
643
644#[server]
645async fn withdraw_for_user_server(user: String, amount: f64) -> Result<(), ServerFnError> {
646 if amount < 0.0 {
647 return Err(ServerFnError::new("Amount cannot be negative."));
648 }
649
650 if let Err(e) = crate::control::enqueue_critical(crate::control::CriticalCommands::Withdraw {
651 name: user,
652 amount: amount,
653 })
654 .await
655 {
656 return Err(ServerFnError::new(format!(
657 "[SERVER] Failed to withdraw : {e}"
658 )));
659 }
660
661 Ok(())
662}
663
664#[server]
665async fn pay_for_user_server(user: String, amount: f64) -> Result<(), ServerFnError> {
666 if amount < 0.0 {
667 return Err(ServerFnError::new("Amount cannot be negative."));
668 }
669
670 if let Err(e) = crate::control::enqueue_critical(crate::control::CriticalCommands::Pay {
671 name: user,
672 amount: amount,
673 })
674 .await
675 {
676 return Err(ServerFnError::new(format!("[SERVER] Failed to pay : {e}")));
677 }
678
679 Ok(())
680}
681
682#[server]
683async fn transfer_from_user_to_user_server(
684 from_user: String,
685 to_user: String,
686 amount: f64,
687 _optional_message: String,
688) -> Result<(), ServerFnError> {
689 if amount < 0.0 {
690 return Err(ServerFnError::new("Amount cannot be negative."));
691 }
692
693 if let Err(e) = crate::control::enqueue_critical(crate::control::CriticalCommands::Transfer {
694 from: from_user,
695 to: to_user,
696 amount: amount,
697 })
698 .await
699 {
700 return Err(ServerFnError::new(format!(
701 "[SERVER] Failed to make the transfer: {e}"
702 )));
703 }
704
705 Ok(())
706}
707
708#[server]
709async fn get_transactions_for_user_server(
710 name: String,
711) -> Result<Vec<crate::db::Transaction>, ServerFnError> {
712 if let Ok(data) = crate::db::get_transactions_for_user(&name) {
713 Ok(data)
714 } else {
715 Err(ServerFnError::new("User not found."))
716 }
717}
718
719#[server]
720async fn refund_transaction_server(
721 name: String,
722 lamport_time: i64,
723 transac_node: String,
724) -> Result<(), ServerFnError> {
725 if let Err(e) = crate::control::enqueue_critical(crate::control::CriticalCommands::Refund {
726 name: name,
727 lamport: lamport_time,
728 node: transac_node,
729 })
730 .await
731 {
732 return Err(ServerFnError::new(format!(
733 "[SERVER] Failed to refund : {e}"
734 )));
735 }
736
737 Ok(())
738}