more refactor wip
This commit is contained in:
parent
6f8fb27221
commit
ddbc9d3a75
@ -3,9 +3,11 @@ pub mod simple_resident_wizard;
|
||||
pub mod simple_step_info;
|
||||
pub mod residence_card;
|
||||
pub mod refactored_resident_wizard;
|
||||
pub mod multi_step_resident_wizard;
|
||||
|
||||
pub use step_payment_stripe::*;
|
||||
pub use simple_resident_wizard::*;
|
||||
pub use simple_step_info::*;
|
||||
pub use residence_card::*;
|
||||
pub use refactored_resident_wizard::*;
|
||||
pub use multi_step_resident_wizard::*;
|
@ -0,0 +1,441 @@
|
||||
//! Resident registration wizard using the generic MultiStepForm component
|
||||
|
||||
use yew::prelude::*;
|
||||
use std::rc::Rc;
|
||||
use std::collections::HashMap;
|
||||
use wasm_bindgen_futures::spawn_local;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::console;
|
||||
use serde_json::json;
|
||||
use js_sys;
|
||||
|
||||
use crate::models::company::{DigitalResidentFormData, DigitalResident, ResidentPaymentPlan};
|
||||
use crate::services::ResidentService;
|
||||
use crate::components::common::forms::{MultiStepForm, FormStep, StepValidator, ValidationResult};
|
||||
use crate::components::common::forms::multi_step_form::MultiStepFormMsg;
|
||||
use super::{SimpleStepInfo, StepPaymentStripe};
|
||||
|
||||
/// Step 1: Personal Information and KYC
|
||||
pub struct PersonalInfoStep;
|
||||
|
||||
impl FormStep<DigitalResidentFormData> for PersonalInfoStep {
|
||||
fn render(&self, ctx: &Context<MultiStepForm<DigitalResidentFormData>>, data: &DigitalResidentFormData) -> Html {
|
||||
let on_change = ctx.link().callback(|new_data| {
|
||||
MultiStepFormMsg::UpdateFormData(new_data)
|
||||
});
|
||||
|
||||
html! {
|
||||
<SimpleStepInfo
|
||||
form_data={data.clone()}
|
||||
on_change={on_change}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
fn get_title(&self) -> &'static str {
|
||||
"Personal Information & KYC"
|
||||
}
|
||||
|
||||
fn get_description(&self) -> &'static str {
|
||||
"Provide your basic information and complete identity verification"
|
||||
}
|
||||
|
||||
fn get_icon(&self) -> &'static str {
|
||||
"bi-person-vcard"
|
||||
}
|
||||
}
|
||||
|
||||
/// Step 2: Payment and Legal Agreements
|
||||
pub struct PaymentStep {
|
||||
client_secret: Option<String>,
|
||||
processing_payment: bool,
|
||||
}
|
||||
|
||||
impl PaymentStep {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
client_secret: None,
|
||||
processing_payment: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_client_secret(&mut self, client_secret: Option<String>) {
|
||||
self.client_secret = client_secret;
|
||||
}
|
||||
|
||||
pub fn set_processing(&mut self, processing: bool) {
|
||||
self.processing_payment = processing;
|
||||
}
|
||||
}
|
||||
|
||||
impl FormStep<DigitalResidentFormData> for PaymentStep {
|
||||
fn render(&self, ctx: &Context<MultiStepForm<DigitalResidentFormData>>, data: &DigitalResidentFormData) -> Html {
|
||||
let on_process_payment = ctx.link().callback(|_| {
|
||||
// This would trigger payment processing
|
||||
MultiStepFormMsg::NextStep
|
||||
});
|
||||
|
||||
let on_payment_complete = ctx.link().callback(|resident: DigitalResident| {
|
||||
// This would complete the form
|
||||
MultiStepFormMsg::Complete
|
||||
});
|
||||
|
||||
let on_payment_error = ctx.link().callback(|error: String| {
|
||||
console::log_1(&format!("Payment error: {}", error).into());
|
||||
// Could trigger validation error display
|
||||
MultiStepFormMsg::HideValidationToast
|
||||
});
|
||||
|
||||
let data_clone = data.clone();
|
||||
let on_payment_plan_change = ctx.link().callback(move |plan: ResidentPaymentPlan| {
|
||||
let mut updated_data = data_clone.clone();
|
||||
updated_data.payment_plan = plan;
|
||||
MultiStepFormMsg::UpdateFormData(updated_data)
|
||||
});
|
||||
|
||||
let on_confirmation_change = ctx.link().callback(|_confirmed: bool| {
|
||||
// Handle confirmation state change
|
||||
MultiStepFormMsg::HideValidationToast
|
||||
});
|
||||
|
||||
html! {
|
||||
<StepPaymentStripe
|
||||
form_data={data.clone()}
|
||||
client_secret={self.client_secret.clone()}
|
||||
processing_payment={self.processing_payment}
|
||||
on_process_payment={on_process_payment}
|
||||
on_payment_complete={on_payment_complete}
|
||||
on_payment_error={on_payment_error}
|
||||
on_payment_plan_change={on_payment_plan_change}
|
||||
on_confirmation_change={on_confirmation_change}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
fn get_title(&self) -> &'static str {
|
||||
"Payment & Legal Agreements"
|
||||
}
|
||||
|
||||
fn get_description(&self) -> &'static str {
|
||||
"Choose your payment plan and review legal agreements"
|
||||
}
|
||||
|
||||
fn get_icon(&self) -> &'static str {
|
||||
"bi-credit-card"
|
||||
}
|
||||
|
||||
fn show_navigation(&self) -> bool {
|
||||
false // Payment step handles its own navigation
|
||||
}
|
||||
}
|
||||
|
||||
/// Validator for personal information step
|
||||
pub struct PersonalInfoValidator;
|
||||
|
||||
impl StepValidator<DigitalResidentFormData> for PersonalInfoValidator {
|
||||
fn validate(&self, data: &DigitalResidentFormData) -> ValidationResult {
|
||||
let mut errors = Vec::new();
|
||||
|
||||
if data.full_name.trim().is_empty() {
|
||||
errors.push("Full name is required".to_string());
|
||||
}
|
||||
|
||||
if data.email.trim().is_empty() {
|
||||
errors.push("Email address is required".to_string());
|
||||
} else if !data.email.contains('@') {
|
||||
errors.push("Please enter a valid email address".to_string());
|
||||
}
|
||||
|
||||
if data.public_key.is_none() {
|
||||
errors.push("Please generate your digital identity keys".to_string());
|
||||
}
|
||||
|
||||
if !data.legal_agreements.terms {
|
||||
errors.push("You must agree to the Terms of Service and Privacy Policy".to_string());
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
ValidationResult::valid()
|
||||
} else {
|
||||
ValidationResult::invalid(errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Validator for payment step
|
||||
pub struct PaymentValidator;
|
||||
|
||||
impl StepValidator<DigitalResidentFormData> for PaymentValidator {
|
||||
fn validate(&self, data: &DigitalResidentFormData) -> ValidationResult {
|
||||
let mut errors = Vec::new();
|
||||
|
||||
// Basic payment validation - in real implementation this would check payment completion
|
||||
if data.payment_plan == ResidentPaymentPlan::Monthly && data.full_name.is_empty() {
|
||||
errors.push("Payment information is incomplete".to_string());
|
||||
}
|
||||
|
||||
if errors.is_empty() {
|
||||
ValidationResult::valid()
|
||||
} else {
|
||||
ValidationResult::invalid(errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Properties for the multi-step resident wizard
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct MultiStepResidentWizardProps {
|
||||
pub on_registration_complete: Callback<DigitalResident>,
|
||||
pub on_back_to_parent: Callback<()>,
|
||||
#[prop_or_default]
|
||||
pub success_resident_id: Option<u32>,
|
||||
#[prop_or_default]
|
||||
pub show_failure: bool,
|
||||
}
|
||||
|
||||
/// Messages for the multi-step resident wizard
|
||||
pub enum MultiStepResidentWizardMsg {
|
||||
FormDataChanged(DigitalResidentFormData),
|
||||
FormCompleted(DigitalResidentFormData),
|
||||
FormCancelled,
|
||||
CreatePaymentIntent,
|
||||
PaymentIntentCreated(String),
|
||||
PaymentIntentError(String),
|
||||
RegistrationComplete(DigitalResident),
|
||||
RegistrationError(String),
|
||||
}
|
||||
|
||||
/// Multi-step resident wizard component
|
||||
pub struct MultiStepResidentWizard {
|
||||
form_data: DigitalResidentFormData,
|
||||
steps: Vec<Rc<dyn FormStep<DigitalResidentFormData>>>,
|
||||
validators: HashMap<usize, Rc<dyn StepValidator<DigitalResidentFormData>>>,
|
||||
client_secret: Option<String>,
|
||||
processing_registration: bool,
|
||||
}
|
||||
|
||||
impl Component for MultiStepResidentWizard {
|
||||
type Message = MultiStepResidentWizardMsg;
|
||||
type Properties = MultiStepResidentWizardProps;
|
||||
|
||||
fn create(ctx: &Context<Self>) -> Self {
|
||||
// Initialize form data based on props
|
||||
let form_data = if ctx.props().success_resident_id.is_some() || ctx.props().show_failure {
|
||||
// For demo purposes, start with default data
|
||||
DigitalResidentFormData::default()
|
||||
} else {
|
||||
DigitalResidentFormData::default()
|
||||
};
|
||||
|
||||
// Create steps
|
||||
let steps: Vec<Rc<dyn FormStep<DigitalResidentFormData>>> = vec![
|
||||
Rc::new(PersonalInfoStep),
|
||||
Rc::new(PaymentStep::new()),
|
||||
];
|
||||
|
||||
// Create validators
|
||||
let mut validators: HashMap<usize, Rc<dyn StepValidator<DigitalResidentFormData>>> = HashMap::new();
|
||||
validators.insert(0, Rc::new(PersonalInfoValidator));
|
||||
validators.insert(1, Rc::new(PaymentValidator));
|
||||
|
||||
Self {
|
||||
form_data,
|
||||
steps,
|
||||
validators,
|
||||
client_secret: None,
|
||||
processing_registration: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||
match msg {
|
||||
MultiStepResidentWizardMsg::FormDataChanged(new_data) => {
|
||||
self.form_data = new_data;
|
||||
true
|
||||
}
|
||||
MultiStepResidentWizardMsg::FormCompleted(final_data) => {
|
||||
console::log_1(&"🎉 Form completed, processing registration...".into());
|
||||
self.processing_registration = true;
|
||||
self.form_data = final_data;
|
||||
|
||||
// Start registration process
|
||||
let link = ctx.link().clone();
|
||||
let form_data = self.form_data.clone();
|
||||
|
||||
spawn_local(async move {
|
||||
// Simulate registration processing with a simple timeout
|
||||
let promise = js_sys::Promise::new(&mut |resolve, _| {
|
||||
let window = web_sys::window().unwrap();
|
||||
window.set_timeout_with_callback_and_timeout_and_arguments_0(&resolve, 2000).unwrap();
|
||||
});
|
||||
let _ = wasm_bindgen_futures::JsFuture::from(promise).await;
|
||||
|
||||
match ResidentService::create_resident_from_form(&form_data) {
|
||||
Ok(resident) => {
|
||||
link.send_message(MultiStepResidentWizardMsg::RegistrationComplete(resident));
|
||||
}
|
||||
Err(error) => {
|
||||
link.send_message(MultiStepResidentWizardMsg::RegistrationError(error));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
true
|
||||
}
|
||||
MultiStepResidentWizardMsg::FormCancelled => {
|
||||
ctx.props().on_back_to_parent.emit(());
|
||||
false
|
||||
}
|
||||
MultiStepResidentWizardMsg::CreatePaymentIntent => {
|
||||
console::log_1(&"🔧 Creating payment intent...".into());
|
||||
self.create_payment_intent(ctx);
|
||||
false
|
||||
}
|
||||
MultiStepResidentWizardMsg::PaymentIntentCreated(client_secret) => {
|
||||
console::log_1(&"✅ Payment intent created".into());
|
||||
self.client_secret = Some(client_secret);
|
||||
|
||||
// Update the payment step with the client secret
|
||||
if let Some(_payment_step) = self.steps.get_mut(1) {
|
||||
// This is a bit tricky with Rc - in a real implementation,
|
||||
// we might use a different pattern for mutable step state
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
MultiStepResidentWizardMsg::PaymentIntentError(error) => {
|
||||
console::log_1(&format!("❌ Payment intent error: {}", error).into());
|
||||
true
|
||||
}
|
||||
MultiStepResidentWizardMsg::RegistrationComplete(resident) => {
|
||||
self.processing_registration = false;
|
||||
console::log_1(&"✅ Registration completed successfully".into());
|
||||
ctx.props().on_registration_complete.emit(resident);
|
||||
true
|
||||
}
|
||||
MultiStepResidentWizardMsg::RegistrationError(error) => {
|
||||
self.processing_registration = false;
|
||||
console::log_1(&format!("❌ Registration error: {}", error).into());
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
let link = ctx.link();
|
||||
|
||||
let on_form_change = link.callback(MultiStepResidentWizardMsg::FormDataChanged);
|
||||
let on_complete = link.callback(MultiStepResidentWizardMsg::FormCompleted);
|
||||
let on_cancel = link.callback(|_| MultiStepResidentWizardMsg::FormCancelled);
|
||||
|
||||
html! {
|
||||
<div class="h-100 d-flex flex-column position-relative">
|
||||
<MultiStepForm<DigitalResidentFormData>
|
||||
form_data={self.form_data.clone()}
|
||||
on_form_change={on_form_change}
|
||||
on_complete={on_complete}
|
||||
on_cancel={Some(on_cancel)}
|
||||
steps={self.steps.clone()}
|
||||
validators={self.validators.clone()}
|
||||
show_progress={true}
|
||||
allow_skip_validation={false}
|
||||
validation_toast_duration={5000}
|
||||
/>
|
||||
|
||||
// Loading overlay when processing registration
|
||||
{if self.processing_registration {
|
||||
html! {
|
||||
<div class="position-absolute top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-center"
|
||||
style="background: rgba(255, 255, 255, 0.9); z-index: 1050;">
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary mb-3" role="status">
|
||||
<span class="visually-hidden">{"Loading..."}</span>
|
||||
</div>
|
||||
<p class="mt-3 text-muted">{"Processing registration..."}</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
} else {
|
||||
html! {}
|
||||
}}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MultiStepResidentWizard {
|
||||
fn create_payment_intent(&self, ctx: &Context<Self>) {
|
||||
let link = ctx.link().clone();
|
||||
let form_data = self.form_data.clone();
|
||||
|
||||
spawn_local(async move {
|
||||
match Self::setup_stripe_payment(form_data).await {
|
||||
Ok(client_secret) => {
|
||||
link.send_message(MultiStepResidentWizardMsg::PaymentIntentCreated(client_secret));
|
||||
}
|
||||
Err(e) => {
|
||||
link.send_message(MultiStepResidentWizardMsg::PaymentIntentError(e));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn setup_stripe_payment(form_data: DigitalResidentFormData) -> Result<String, String> {
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use web_sys::{Request, RequestInit, RequestMode, Response};
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
console::log_1(&"🔧 Setting up Stripe payment for resident registration".into());
|
||||
|
||||
// Prepare form data for payment intent creation
|
||||
let payment_data = json!({
|
||||
"resident_name": form_data.full_name,
|
||||
"email": form_data.email,
|
||||
"payment_plan": form_data.payment_plan.get_display_name(),
|
||||
"amount": form_data.payment_plan.get_price(),
|
||||
"type": "resident_registration"
|
||||
});
|
||||
|
||||
// Create request to server endpoint
|
||||
let mut opts = RequestInit::new();
|
||||
opts.method("POST");
|
||||
opts.mode(RequestMode::Cors);
|
||||
|
||||
let headers = web_sys::js_sys::Map::new();
|
||||
headers.set(&"Content-Type".into(), &"application/json".into());
|
||||
opts.headers(&headers);
|
||||
|
||||
opts.body(Some(&JsValue::from_str(&payment_data.to_string())));
|
||||
|
||||
let request = Request::new_with_str_and_init(
|
||||
"http://127.0.0.1:3001/resident/create-payment-intent",
|
||||
&opts,
|
||||
).map_err(|e| format!("Failed to create request: {:?}", e))?;
|
||||
|
||||
// Make the request
|
||||
let window = web_sys::window().unwrap();
|
||||
let resp_value = JsFuture::from(window.fetch_with_request(&request)).await
|
||||
.map_err(|e| format!("Network request failed: {:?}", e))?;
|
||||
|
||||
let resp: Response = resp_value.dyn_into().unwrap();
|
||||
|
||||
if !resp.ok() {
|
||||
return Err(format!("Server error: HTTP {}", resp.status()));
|
||||
}
|
||||
|
||||
// Parse response
|
||||
let json_value = JsFuture::from(resp.json().unwrap()).await
|
||||
.map_err(|e| format!("Failed to parse response: {:?}", e))?;
|
||||
|
||||
// Extract client secret from response
|
||||
let response_obj = web_sys::js_sys::Object::from(json_value);
|
||||
let client_secret_value = web_sys::js_sys::Reflect::get(&response_obj, &"client_secret".into())
|
||||
.map_err(|e| format!("No client_secret in response: {:?}", e))?;
|
||||
|
||||
let client_secret = client_secret_value.as_string()
|
||||
.ok_or_else(|| "Invalid client secret received from server".to_string())?;
|
||||
|
||||
console::log_1(&"✅ Payment intent created successfully".into());
|
||||
Ok(client_secret)
|
||||
}
|
||||
}
|
@ -6,6 +6,9 @@ use web_sys::{console, js_sys};
|
||||
use serde_json::json;
|
||||
use crate::models::company::{DigitalResidentFormData, DigitalResident, ResidentPaymentPlan};
|
||||
use crate::services::{ResidentService, ResidentRegistration, ResidentRegistrationStatus};
|
||||
use crate::components::common::ui::progress_indicator::{ProgressIndicator, ProgressVariant, ProgressColor, ProgressSize};
|
||||
use crate::components::common::ui::validation_toast::{ValidationToast, ToastType};
|
||||
use crate::components::common::ui::loading_spinner::LoadingSpinner;
|
||||
use super::{SimpleStepInfo, StepPaymentStripe};
|
||||
|
||||
#[wasm_bindgen]
|
||||
@ -217,7 +220,7 @@ impl Component for SimpleResidentWizard {
|
||||
let (step_title, step_description, step_icon) = self.get_step_info();
|
||||
|
||||
html! {
|
||||
<div class="h-100 d-flex flex-column">
|
||||
<div class="h-100 d-flex flex-column position-relative">
|
||||
<form class="flex-grow-1 overflow-auto">
|
||||
{self.render_current_step(ctx)}
|
||||
</form>
|
||||
@ -233,6 +236,21 @@ impl Component for SimpleResidentWizard {
|
||||
} else {
|
||||
html! {}
|
||||
}}
|
||||
|
||||
// Loading overlay when processing registration
|
||||
{if self.processing_registration {
|
||||
html! {
|
||||
<div class="position-absolute top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-center"
|
||||
style="background: rgba(255, 255, 255, 0.9); z-index: 1050;">
|
||||
<div class="text-center">
|
||||
<LoadingSpinner />
|
||||
<p class="mt-3 text-muted">{"Processing registration..."}</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
} else {
|
||||
html! {}
|
||||
}}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@ -295,40 +313,16 @@ impl SimpleResidentWizard {
|
||||
}}
|
||||
</div>
|
||||
|
||||
// Step indicator (center)
|
||||
// Step indicator (center) - Using our generic ProgressIndicator
|
||||
<div class="d-flex align-items-center">
|
||||
{for (1..=2).map(|step| {
|
||||
let is_current = step == self.current_step;
|
||||
let is_completed = step < self.current_step;
|
||||
let step_class = if is_current {
|
||||
"bg-primary text-white"
|
||||
} else if is_completed {
|
||||
"bg-success text-white"
|
||||
} else {
|
||||
"bg-white text-muted border"
|
||||
};
|
||||
|
||||
html! {
|
||||
<div class="d-flex align-items-center">
|
||||
<div class={format!("rounded-circle d-flex align-items-center justify-content-center {} fw-bold", step_class)}
|
||||
style="width: 28px; height: 28px; font-size: 12px;">
|
||||
{if is_completed {
|
||||
html! { <i class="bi bi-check"></i> }
|
||||
} else {
|
||||
html! { {step} }
|
||||
}}
|
||||
</div>
|
||||
{if step < 2 {
|
||||
html! {
|
||||
<div class={format!("mx-1 {}", if is_completed { "bg-success" } else { "bg-secondary" })}
|
||||
style="height: 2px; width: 24px;"></div>
|
||||
}
|
||||
} else {
|
||||
html! {}
|
||||
}}
|
||||
</div>
|
||||
}
|
||||
})}
|
||||
<ProgressIndicator
|
||||
current_step={self.current_step as usize - 1} // Convert to 0-based index
|
||||
total_steps={2}
|
||||
variant={ProgressVariant::Dots}
|
||||
color={ProgressColor::Primary}
|
||||
size={ProgressSize::Small}
|
||||
show_step_numbers={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
// Next/Register button (right)
|
||||
@ -362,29 +356,13 @@ impl SimpleResidentWizard {
|
||||
let close_toast = link.callback(|_| SimpleResidentWizardMsg::HideValidationToast);
|
||||
|
||||
html! {
|
||||
<div class="position-fixed bottom-0 start-50 translate-middle-x mb-3" style="z-index: 1055; max-width: 500px;">
|
||||
<div class="toast show" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-header bg-warning text-dark">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
<strong class="me-auto">{"Required Fields Missing"}</strong>
|
||||
<button type="button" class="btn-close" onclick={close_toast} aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="toast-body">
|
||||
<div class="mb-2">
|
||||
<strong>{"Please complete all required fields to continue:"}</strong>
|
||||
</div>
|
||||
<ul class="list-unstyled mb-0">
|
||||
{for self.validation_errors.iter().map(|error| {
|
||||
html! {
|
||||
<li class="mb-1">
|
||||
<i class="bi bi-dot text-danger me-1"></i>{error}
|
||||
</li>
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ValidationToast
|
||||
toast_type={ToastType::Warning}
|
||||
title={"Required Fields Missing"}
|
||||
messages={self.validation_errors.clone()}
|
||||
show={self.show_validation_toast}
|
||||
on_close={close_toast}
|
||||
/>
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ use wasm_bindgen_futures::spawn_local;
|
||||
use web_sys::{window, console, js_sys};
|
||||
use crate::models::company::{DigitalResidentFormData, DigitalResident, ResidentPaymentPlan};
|
||||
use crate::services::ResidentService;
|
||||
use crate::components::common::ui::loading_spinner::LoadingSpinner;
|
||||
use super::ResidenceCard;
|
||||
|
||||
#[wasm_bindgen]
|
||||
@ -175,19 +176,15 @@ impl Component for StepPaymentStripe {
|
||||
{if ctx.props().processing_payment {
|
||||
html! {
|
||||
<div class="text-center py-4">
|
||||
<div class="spinner-border text-secondary mb-3" role="status" style="width: 1.5rem; height: 1.5rem;">
|
||||
<span class="visually-hidden">{"Loading..."}</span>
|
||||
</div>
|
||||
<p class="text-muted" style="font-size: 0.85rem;">{"Processing payment..."}</p>
|
||||
<LoadingSpinner />
|
||||
<p class="text-muted mt-3" style="font-size: 0.85rem;">{"Processing payment..."}</p>
|
||||
</div>
|
||||
}
|
||||
} else if !has_client_secret {
|
||||
html! {
|
||||
<div class="text-center py-4">
|
||||
<div class="spinner-border text-secondary mb-3" role="status" style="width: 1.5rem; height: 1.5rem;">
|
||||
<span class="visually-hidden">{"Loading..."}</span>
|
||||
</div>
|
||||
<p class="text-muted" style="font-size: 0.85rem;">{"Preparing payment form..."}</p>
|
||||
<LoadingSpinner />
|
||||
<p class="text-muted mt-3" style="font-size: 0.85rem;">{"Preparing payment form..."}</p>
|
||||
</div>
|
||||
}
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user