initial commit
This commit is contained in:
755
platform/src/views/administration_view.rs
Normal file
755
platform/src/views/administration_view.rs
Normal file
@@ -0,0 +1,755 @@
|
||||
use yew::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
use crate::routing::ViewContext;
|
||||
use crate::components::{ViewComponent, EmptyState};
|
||||
use crate::services::mock_billing_api::{MockBillingApi, Plan};
|
||||
use web_sys::MouseEvent;
|
||||
use wasm_bindgen::JsCast;
|
||||
use gloo::timers::callback::Timeout;
|
||||
|
||||
#[derive(Properties, PartialEq)]
|
||||
pub struct AdministrationViewProps {
|
||||
pub context: ViewContext,
|
||||
}
|
||||
|
||||
#[function_component(AdministrationView)]
|
||||
pub fn administration_view(props: &AdministrationViewProps) -> Html {
|
||||
// Initialize mock billing API
|
||||
let billing_api = use_state(|| MockBillingApi::new());
|
||||
|
||||
// State for managing UI interactions
|
||||
let show_plan_modal = use_state(|| false);
|
||||
let show_cancel_modal = use_state(|| false);
|
||||
let show_add_payment_modal = use_state(|| false);
|
||||
let downloading_invoice = use_state(|| None::<String>);
|
||||
let selected_plan = use_state(|| None::<String>);
|
||||
let loading_action = use_state(|| None::<String>);
|
||||
|
||||
// Event handlers
|
||||
let on_change_plan = {
|
||||
let show_plan_modal = show_plan_modal.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
show_plan_modal.set(true);
|
||||
})
|
||||
};
|
||||
|
||||
let on_cancel_subscription = {
|
||||
let show_cancel_modal = show_cancel_modal.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
show_cancel_modal.set(true);
|
||||
})
|
||||
};
|
||||
|
||||
let on_confirm_cancel_subscription = {
|
||||
let billing_api = billing_api.clone();
|
||||
let show_cancel_modal = show_cancel_modal.clone();
|
||||
let loading_action = loading_action.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
loading_action.set(Some("canceling".to_string()));
|
||||
|
||||
let billing_api_clone = billing_api.clone();
|
||||
let show_cancel_modal_clone = show_cancel_modal.clone();
|
||||
let loading_action_clone = loading_action.clone();
|
||||
|
||||
// Simulate async operation with timeout
|
||||
Timeout::new(1000, move || {
|
||||
let mut api = (*billing_api_clone).clone();
|
||||
api.current_subscription.status = "cancelled".to_string();
|
||||
billing_api_clone.set(api);
|
||||
loading_action_clone.set(None);
|
||||
show_cancel_modal_clone.set(false);
|
||||
web_sys::console::log_1(&"Subscription canceled successfully".into());
|
||||
}).forget();
|
||||
})
|
||||
};
|
||||
|
||||
let on_download_invoice = {
|
||||
let billing_api = billing_api.clone();
|
||||
let downloading_invoice = downloading_invoice.clone();
|
||||
Callback::from(move |e: MouseEvent| {
|
||||
if let Some(target) = e.target() {
|
||||
if let Ok(button) = target.dyn_into::<web_sys::HtmlElement>() {
|
||||
if let Some(invoice_id) = button.get_attribute("data-invoice-id") {
|
||||
downloading_invoice.set(Some(invoice_id.clone()));
|
||||
|
||||
let billing_api_clone = billing_api.clone();
|
||||
let downloading_invoice_clone = downloading_invoice.clone();
|
||||
let invoice_id_clone = invoice_id.clone();
|
||||
|
||||
// Simulate download with timeout
|
||||
Timeout::new(500, move || {
|
||||
let api = (*billing_api_clone).clone();
|
||||
|
||||
// Find the invoice and get its PDF URL
|
||||
if let Some(invoice) = api.invoices.iter().find(|i| i.id == invoice_id_clone) {
|
||||
// Create a link and trigger download
|
||||
if let Some(window) = web_sys::window() {
|
||||
if let Some(document) = window.document() {
|
||||
if let Ok(anchor) = document.create_element("a") {
|
||||
if let Ok(anchor) = anchor.dyn_into::<web_sys::HtmlElement>() {
|
||||
anchor.set_attribute("href", &invoice.pdf_url).unwrap();
|
||||
anchor.set_attribute("download", &format!("invoice_{}.pdf", invoice_id_clone)).unwrap();
|
||||
anchor.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
web_sys::console::log_1(&"Invoice downloaded successfully".into());
|
||||
} else {
|
||||
web_sys::console::log_1(&"Invoice not found".into());
|
||||
}
|
||||
downloading_invoice_clone.set(None);
|
||||
}).forget();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let on_add_payment_method = {
|
||||
let show_add_payment_modal = show_add_payment_modal.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
show_add_payment_modal.set(true);
|
||||
})
|
||||
};
|
||||
|
||||
let on_confirm_add_payment_method = {
|
||||
let billing_api = billing_api.clone();
|
||||
let show_add_payment_modal = show_add_payment_modal.clone();
|
||||
let loading_action = loading_action.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
loading_action.set(Some("adding_payment".to_string()));
|
||||
|
||||
let billing_api_clone = billing_api.clone();
|
||||
let show_add_payment_modal_clone = show_add_payment_modal.clone();
|
||||
let loading_action_clone = loading_action.clone();
|
||||
|
||||
// Simulate async operation with timeout
|
||||
Timeout::new(1000, move || {
|
||||
let mut api = (*billing_api_clone).clone();
|
||||
|
||||
// Add a new payment method
|
||||
let new_method = crate::services::mock_billing_api::PaymentMethod {
|
||||
id: format!("card_{}", api.payment_methods.len() + 1),
|
||||
method_type: "Credit Card".to_string(),
|
||||
last_four: "•••• •••• •••• 4242".to_string(),
|
||||
expires: Some("12/28".to_string()),
|
||||
is_primary: false,
|
||||
};
|
||||
|
||||
api.payment_methods.push(new_method);
|
||||
billing_api_clone.set(api);
|
||||
loading_action_clone.set(None);
|
||||
show_add_payment_modal_clone.set(false);
|
||||
web_sys::console::log_1(&"Payment method added successfully".into());
|
||||
}).forget();
|
||||
})
|
||||
};
|
||||
|
||||
let on_edit_payment_method = {
|
||||
let loading_action = loading_action.clone();
|
||||
Callback::from(move |e: MouseEvent| {
|
||||
if let Some(target) = e.target() {
|
||||
if let Ok(button) = target.dyn_into::<web_sys::HtmlElement>() {
|
||||
if let Some(method_id) = button.get_attribute("data-method") {
|
||||
let loading_action_clone = loading_action.clone();
|
||||
let method_id_clone = method_id.clone();
|
||||
|
||||
loading_action.set(Some(format!("editing_{}", method_id)));
|
||||
|
||||
// Simulate API call delay
|
||||
Timeout::new(1000, move || {
|
||||
loading_action_clone.set(None);
|
||||
web_sys::console::log_1(&format!("Edit payment method: {}", method_id_clone).into());
|
||||
}).forget();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let on_remove_payment_method = {
|
||||
let billing_api = billing_api.clone();
|
||||
let loading_action = loading_action.clone();
|
||||
Callback::from(move |e: MouseEvent| {
|
||||
if let Some(target) = e.target() {
|
||||
if let Ok(button) = target.dyn_into::<web_sys::HtmlElement>() {
|
||||
if let Some(method_id) = button.get_attribute("data-method") {
|
||||
if web_sys::window()
|
||||
.unwrap()
|
||||
.confirm_with_message(&format!("Are you sure you want to remove this payment method?"))
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let billing_api_clone = billing_api.clone();
|
||||
let loading_action_clone = loading_action.clone();
|
||||
let method_id_clone = method_id.clone();
|
||||
|
||||
loading_action.set(Some(format!("removing_{}", method_id)));
|
||||
|
||||
// Simulate async operation with timeout
|
||||
Timeout::new(1000, move || {
|
||||
let mut api = (*billing_api_clone).clone();
|
||||
|
||||
// Remove the payment method
|
||||
if let Some(pos) = api.payment_methods.iter().position(|m| m.id == method_id_clone) {
|
||||
api.payment_methods.remove(pos);
|
||||
billing_api_clone.set(api);
|
||||
web_sys::console::log_1(&"Payment method removed successfully".into());
|
||||
} else {
|
||||
web_sys::console::log_1(&"Payment method not found".into());
|
||||
}
|
||||
|
||||
loading_action_clone.set(None);
|
||||
}).forget();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let on_select_plan = {
|
||||
let selected_plan = selected_plan.clone();
|
||||
Callback::from(move |e: MouseEvent| {
|
||||
if let Some(target) = e.target() {
|
||||
if let Ok(button) = target.dyn_into::<web_sys::HtmlElement>() {
|
||||
if let Some(plan_id) = button.get_attribute("data-plan-id") {
|
||||
selected_plan.set(Some(plan_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let on_confirm_plan_change = {
|
||||
let billing_api = billing_api.clone();
|
||||
let selected_plan = selected_plan.clone();
|
||||
let show_plan_modal = show_plan_modal.clone();
|
||||
let loading_action = loading_action.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
if let Some(plan_id) = (*selected_plan).clone() {
|
||||
loading_action.set(Some("changing_plan".to_string()));
|
||||
|
||||
let billing_api_clone = billing_api.clone();
|
||||
let show_plan_modal_clone = show_plan_modal.clone();
|
||||
let loading_action_clone = loading_action.clone();
|
||||
let plan_id_clone = plan_id.clone();
|
||||
|
||||
// Simulate async operation with timeout
|
||||
Timeout::new(1000, move || {
|
||||
let mut api = (*billing_api_clone).clone();
|
||||
|
||||
// Change the plan
|
||||
if let Some(plan) = api.available_plans.iter().find(|p| p.id == plan_id_clone) {
|
||||
api.current_subscription.plan = plan.clone();
|
||||
billing_api_clone.set(api);
|
||||
web_sys::console::log_1(&"Plan changed successfully".into());
|
||||
} else {
|
||||
web_sys::console::log_1(&"Plan not found".into());
|
||||
}
|
||||
|
||||
loading_action_clone.set(None);
|
||||
show_plan_modal_clone.set(false);
|
||||
}).forget();
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let close_modals = {
|
||||
let show_plan_modal = show_plan_modal.clone();
|
||||
let show_cancel_modal = show_cancel_modal.clone();
|
||||
let show_add_payment_modal = show_add_payment_modal.clone();
|
||||
let selected_plan = selected_plan.clone();
|
||||
Callback::from(move |_: MouseEvent| {
|
||||
show_plan_modal.set(false);
|
||||
show_cancel_modal.set(false);
|
||||
show_add_payment_modal.set(false);
|
||||
selected_plan.set(None);
|
||||
})
|
||||
};
|
||||
// Create tabs content
|
||||
let mut tabs = HashMap::new();
|
||||
|
||||
// Organization Setup Tab
|
||||
tabs.insert("Organization Setup".to_string(), html! {
|
||||
<EmptyState
|
||||
icon={"building".to_string()}
|
||||
title={"Organization not configured".to_string()}
|
||||
description={"Set up your organization structure, hierarchy, and basic settings to get started.".to_string()}
|
||||
primary_action={Some(("Setup Organization".to_string(), "#".to_string()))}
|
||||
secondary_action={Some(("Import Settings".to_string(), "#".to_string()))}
|
||||
/>
|
||||
});
|
||||
|
||||
// Shareholders Tab
|
||||
tabs.insert("Shareholders".to_string(), html! {
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-people me-2"></i>
|
||||
{"Shareholder Information"}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{"Name"}</th>
|
||||
<th>{"Ownership %"}</th>
|
||||
<th>{"Shares"}</th>
|
||||
<th>{"Type"}</th>
|
||||
<th>{"Status"}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="bg-primary rounded-circle d-flex align-items-center justify-content-center me-2" style="width: 32px; height: 32px;">
|
||||
<i class="bi bi-person text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold">{"John Doe"}</div>
|
||||
<small class="text-muted">{"Founder & CEO"}</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="fw-bold">{"65%"}</span></td>
|
||||
<td>{"6,500"}</td>
|
||||
<td><span class="badge bg-primary">{"Ordinary"}</span></td>
|
||||
<td><span class="badge bg-success">{"Active"}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="bg-info rounded-circle d-flex align-items-center justify-content-center me-2" style="width: 32px; height: 32px;">
|
||||
<i class="bi bi-person text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold">{"Sarah Johnson"}</div>
|
||||
<small class="text-muted">{"Co-Founder & CTO"}</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="fw-bold">{"25%"}</span></td>
|
||||
<td>{"2,500"}</td>
|
||||
<td><span class="badge bg-primary">{"Ordinary"}</span></td>
|
||||
<td><span class="badge bg-success">{"Active"}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="bg-warning rounded-circle d-flex align-items-center justify-content-center me-2" style="width: 32px; height: 32px;">
|
||||
<i class="bi bi-building text-dark"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold">{"Innovation Ventures"}</div>
|
||||
<small class="text-muted">{"Investment Fund"}</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="fw-bold">{"10%"}</span></td>
|
||||
<td>{"1,000"}</td>
|
||||
<td><span class="badge bg-warning text-dark">{"Preferred"}</span></td>
|
||||
<td><span class="badge bg-success">{"Active"}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<small class="text-muted">
|
||||
<i class="bi bi-info-circle me-1"></i>
|
||||
{"Total Authorized Shares: 10,000 | Issued Shares: 10,000 | Par Value: $1.00"}
|
||||
</small>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-primary">
|
||||
<i class="bi bi-person-plus me-1"></i>
|
||||
{"Add Shareholder"}
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary">
|
||||
<i class="bi bi-download me-1"></i>
|
||||
{"Export Cap Table"}
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary">
|
||||
<i class="bi bi-file-earmark-pdf me-1"></i>
|
||||
{"Generate Certificate"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
});
|
||||
|
||||
// Members & Roles Tab
|
||||
tabs.insert("Members & Roles".to_string(), html! {
|
||||
<EmptyState
|
||||
icon={"person-badge".to_string()}
|
||||
title={"No team members found".to_string()}
|
||||
description={"Invite team members, assign roles, and control access permissions for your organization.".to_string()}
|
||||
primary_action={Some(("Invite Members".to_string(), "#".to_string()))}
|
||||
secondary_action={Some(("Manage Roles".to_string(), "#".to_string()))}
|
||||
/>
|
||||
});
|
||||
|
||||
// Integrations Tab
|
||||
tabs.insert("Integrations".to_string(), html! {
|
||||
<EmptyState
|
||||
icon={"diagram-3".to_string()}
|
||||
title={"No integrations configured".to_string()}
|
||||
description={"Connect with external services and configure API integrations to streamline your workflow.".to_string()}
|
||||
primary_action={Some(("Browse Integrations".to_string(), "#".to_string()))}
|
||||
secondary_action={Some(("API Documentation".to_string(), "#".to_string()))}
|
||||
/>
|
||||
});
|
||||
|
||||
// Billing and Payments Tab
|
||||
tabs.insert("Billing and Payments".to_string(), {
|
||||
let current_subscription = &billing_api.current_subscription;
|
||||
let current_plan = ¤t_subscription.plan;
|
||||
|
||||
html! {
|
||||
<div class="row">
|
||||
// Subscription Tier Pane
|
||||
<div class="col-lg-4 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-star me-2"></i>
|
||||
{"Current Plan"}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="text-center mb-3">
|
||||
<div class="badge bg-primary fs-6 px-3 py-2 mb-2">{¤t_plan.name}</div>
|
||||
<h3 class="text-primary mb-0">{format!("${:.0}", current_plan.price)}<small class="text-muted">{"/month"}</small></h3>
|
||||
</div>
|
||||
<ul class="list-unstyled">
|
||||
{for current_plan.features.iter().map(|feature| html! {
|
||||
<li class="mb-2">
|
||||
<i class="bi bi-check-circle text-success me-2"></i>
|
||||
{feature}
|
||||
</li>
|
||||
})}
|
||||
</ul>
|
||||
<div class="mt-3">
|
||||
<small class="text-muted">{format!("Status: {}", current_subscription.status)}</small>
|
||||
</div>
|
||||
<div class="mt-3 d-grid gap-2">
|
||||
<button
|
||||
class="btn btn-outline-primary btn-sm"
|
||||
onclick={on_change_plan.clone()}
|
||||
disabled={loading_action.as_ref().map_or(false, |action| action == "changing_plan")}
|
||||
>
|
||||
{if loading_action.as_ref().map_or(false, |action| action == "changing_plan") {
|
||||
"Changing..."
|
||||
} else {
|
||||
"Change Plan"
|
||||
}}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-outline-secondary btn-sm"
|
||||
onclick={on_cancel_subscription.clone()}
|
||||
disabled={loading_action.as_ref().map_or(false, |action| action == "canceling")}
|
||||
>
|
||||
{if loading_action.as_ref().map_or(false, |action| action == "canceling") {
|
||||
"Canceling..."
|
||||
} else {
|
||||
"Cancel Subscription"
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-8">
|
||||
// Payments Table Pane
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-receipt me-2"></i>
|
||||
{"Payment History"}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{"Date"}</th>
|
||||
<th>{"Description"}</th>
|
||||
<th>{"Amount"}</th>
|
||||
<th>{"Status"}</th>
|
||||
<th>{"Invoice"}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{for billing_api.invoices.iter().map(|invoice| html! {
|
||||
<tr>
|
||||
<td>{&invoice.date}</td>
|
||||
<td>{&invoice.description}</td>
|
||||
<td>{format!("${:.2}", invoice.amount)}</td>
|
||||
<td><span class="badge bg-success">{&invoice.status}</span></td>
|
||||
<td>
|
||||
<button
|
||||
class="btn btn-outline-secondary btn-sm"
|
||||
onclick={on_download_invoice.clone()}
|
||||
data-invoice-id={invoice.id.clone()}
|
||||
disabled={downloading_invoice.as_ref().map_or(false, |id| id == &invoice.id)}
|
||||
>
|
||||
<i class={if downloading_invoice.as_ref().map_or(false, |id| id == &invoice.id) { "bi bi-arrow-repeat" } else { "bi bi-download" }}></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// Payment Methods Pane
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-credit-card me-2"></i>
|
||||
{"Payment Methods"}
|
||||
</h5>
|
||||
<button
|
||||
class="btn btn-primary btn-sm"
|
||||
onclick={on_add_payment_method.clone()}
|
||||
disabled={loading_action.as_ref().map_or(false, |action| action == "adding_payment")}
|
||||
>
|
||||
<i class="bi bi-plus me-1"></i>
|
||||
{if loading_action.as_ref().map_or(false, |action| action == "adding_payment") {
|
||||
"Adding..."
|
||||
} else {
|
||||
"Add Method"
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
{for billing_api.payment_methods.iter().map(|method| html! {
|
||||
<div class="col-md-6 mb-3">
|
||||
<div class="card border">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class={format!("bg-{} rounded me-3 d-flex align-items-center justify-content-center",
|
||||
if method.method_type == "card" { "primary" } else { "info" })}
|
||||
style="width: 40px; height: 25px;">
|
||||
<i class={format!("bi bi-{} text-white",
|
||||
if method.method_type == "card" { "credit-card" } else { "bank" })}></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-bold">{&method.last_four}</div>
|
||||
<small class="text-muted">{&method.expires}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class={format!("badge bg-{}",
|
||||
if method.is_primary { "success" } else { "secondary" })}>
|
||||
{if method.is_primary { "Primary" } else { "Backup" }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<button
|
||||
class="btn btn-outline-secondary btn-sm me-2"
|
||||
onclick={on_edit_payment_method.clone()}
|
||||
data-method={method.id.clone()}
|
||||
disabled={loading_action.as_ref().map_or(false, |action| action == &format!("editing_{}", method.id))}
|
||||
>
|
||||
{if loading_action.as_ref().map_or(false, |action| action == &format!("editing_{}", method.id)) {
|
||||
"Editing..."
|
||||
} else {
|
||||
"Edit"
|
||||
}}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-outline-danger btn-sm"
|
||||
onclick={on_remove_payment_method.clone()}
|
||||
data-method={method.id.clone()}
|
||||
disabled={loading_action.as_ref().map_or(false, |action| action == &format!("removing_{}", method.id))}
|
||||
>
|
||||
{if loading_action.as_ref().map_or(false, |action| action == &format!("removing_{}", method.id)) {
|
||||
"Removing..."
|
||||
} else {
|
||||
"Remove"
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
});
|
||||
|
||||
html! {
|
||||
<>
|
||||
<ViewComponent
|
||||
title={Some("Administration".to_string())}
|
||||
description={Some("Org setup, members, roles, integrations".to_string())}
|
||||
tabs={Some(tabs)}
|
||||
default_tab={Some("Organization Setup".to_string())}
|
||||
/>
|
||||
|
||||
// Plan Selection Modal
|
||||
if *show_plan_modal {
|
||||
<div class="modal fade show" style="display: block;" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{"Change Plan"}</h5>
|
||||
<button type="button" class="btn-close" onclick={close_modals.clone()}></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
{for billing_api.available_plans.iter().map(|plan| html! {
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class={format!("card h-100 {}",
|
||||
if selected_plan.as_ref().map_or(false, |id| id == &plan.id) { "border-primary" } else { "" })}>
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title">{&plan.name}</h5>
|
||||
<h3 class="text-primary">{format!("${:.0}", plan.price)}<small class="text-muted">{"/month"}</small></h3>
|
||||
<ul class="list-unstyled mt-3">
|
||||
{for plan.features.iter().map(|feature| html! {
|
||||
<li class="mb-1">
|
||||
<i class="bi bi-check text-success me-1"></i>
|
||||
{feature}
|
||||
</li>
|
||||
})}
|
||||
</ul>
|
||||
<button
|
||||
class={format!("btn btn-{} w-100",
|
||||
if selected_plan.as_ref().map_or(false, |id| id == &plan.id) { "primary" } else { "outline-primary" })}
|
||||
onclick={on_select_plan.clone()}
|
||||
data-plan-id={plan.id.clone()}
|
||||
>
|
||||
{if selected_plan.as_ref().map_or(false, |id| id == &plan.id) { "Selected" } else { "Select" }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick={close_modals.clone()}>{"Cancel"}</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
onclick={on_confirm_plan_change.clone()}
|
||||
disabled={selected_plan.is_none() || loading_action.as_ref().map_or(false, |action| action == "changing_plan")}
|
||||
>
|
||||
{if loading_action.as_ref().map_or(false, |action| action == "changing_plan") {
|
||||
"Changing..."
|
||||
} else {
|
||||
"Change Plan"
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
// Cancel Subscription Modal
|
||||
if *show_cancel_modal {
|
||||
<div class="modal fade show" style="display: block;" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{"Cancel Subscription"}</h5>
|
||||
<button type="button" class="btn-close" onclick={close_modals.clone()}></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>{"Are you sure you want to cancel your subscription? This action cannot be undone."}</p>
|
||||
<div class="alert alert-warning">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
{"Your subscription will remain active until the end of the current billing period."}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick={close_modals.clone()}>{"Keep Subscription"}</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-danger"
|
||||
onclick={on_confirm_cancel_subscription.clone()}
|
||||
disabled={loading_action.as_ref().map_or(false, |action| action == "canceling")}
|
||||
>
|
||||
{if loading_action.as_ref().map_or(false, |action| action == "canceling") {
|
||||
"Canceling..."
|
||||
} else {
|
||||
"Cancel Subscription"
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
// Add Payment Method Modal
|
||||
if *show_add_payment_modal {
|
||||
<div class="modal fade show" style="display: block;" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{"Add Payment Method"}</h5>
|
||||
<button type="button" class="btn-close" onclick={close_modals.clone()}></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{"Card Number"}</label>
|
||||
<input type="text" class="form-control" placeholder="1234 5678 9012 3456" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">{"Expiry Date"}</label>
|
||||
<input type="text" class="form-control" placeholder="MM/YY" />
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">{"CVC"}</label>
|
||||
<input type="text" class="form-control" placeholder="123" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{"Cardholder Name"}</label>
|
||||
<input type="text" class="form-control" placeholder="John Doe" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick={close_modals.clone()}>{"Cancel"}</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
onclick={on_confirm_add_payment_method.clone()}
|
||||
disabled={loading_action.as_ref().map_or(false, |action| action == "adding_payment")}
|
||||
>
|
||||
{if loading_action.as_ref().map_or(false, |action| action == "adding_payment") {
|
||||
"Adding..."
|
||||
} else {
|
||||
"Add Payment Method"
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user