checkpoint
This commit is contained in:
parent
fdbb4b84c3
commit
1c96fa4087
267
platform/src/components/inbox.rs
Normal file
267
platform/src/components/inbox.rs
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub struct NotificationItem {
|
||||||
|
pub id: String,
|
||||||
|
pub title: String,
|
||||||
|
pub message: String,
|
||||||
|
pub notification_type: NotificationType,
|
||||||
|
pub timestamp: String,
|
||||||
|
pub is_read: bool,
|
||||||
|
pub action_required: bool,
|
||||||
|
pub action_text: Option<String>,
|
||||||
|
pub action_url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub enum NotificationType {
|
||||||
|
Success,
|
||||||
|
Info,
|
||||||
|
Warning,
|
||||||
|
Action,
|
||||||
|
Vote,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NotificationType {
|
||||||
|
pub fn get_icon(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
NotificationType::Success => "bi-check-circle-fill",
|
||||||
|
NotificationType::Info => "bi-info-circle-fill",
|
||||||
|
NotificationType::Warning => "bi-exclamation-triangle-fill",
|
||||||
|
NotificationType::Action => "bi-bell-fill",
|
||||||
|
NotificationType::Vote => "bi-hand-thumbs-up-fill",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_color(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
NotificationType::Success => "text-success",
|
||||||
|
NotificationType::Info => "text-info",
|
||||||
|
NotificationType::Warning => "text-warning",
|
||||||
|
NotificationType::Action => "text-primary",
|
||||||
|
NotificationType::Vote => "text-purple",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_bg_color(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
NotificationType::Success => "bg-success-subtle",
|
||||||
|
NotificationType::Info => "bg-info-subtle",
|
||||||
|
NotificationType::Warning => "bg-warning-subtle",
|
||||||
|
NotificationType::Action => "bg-primary-subtle",
|
||||||
|
NotificationType::Vote => "bg-purple-subtle",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct InboxProps {
|
||||||
|
#[prop_or_default]
|
||||||
|
pub notifications: Vec<NotificationItem>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component(Inbox)]
|
||||||
|
pub fn inbox(props: &InboxProps) -> Html {
|
||||||
|
// Mock notifications for demo
|
||||||
|
let notifications = if props.notifications.is_empty() {
|
||||||
|
vec![
|
||||||
|
NotificationItem {
|
||||||
|
id: "1".to_string(),
|
||||||
|
title: "Company Registration Successful".to_string(),
|
||||||
|
message: "Your company 'TechCorp FZC' has been successfully registered.".to_string(),
|
||||||
|
notification_type: NotificationType::Success,
|
||||||
|
timestamp: "2 hours ago".to_string(),
|
||||||
|
is_read: true,
|
||||||
|
action_required: false,
|
||||||
|
action_text: Some("View Company".to_string()),
|
||||||
|
action_url: Some("/companies/1".to_string()),
|
||||||
|
},
|
||||||
|
NotificationItem {
|
||||||
|
id: "2".to_string(),
|
||||||
|
title: "Vote Required".to_string(),
|
||||||
|
message: "New governance proposal requires your vote: 'Budget Allocation Q1 2025'".to_string(),
|
||||||
|
notification_type: NotificationType::Vote,
|
||||||
|
timestamp: "1 day ago".to_string(),
|
||||||
|
is_read: true,
|
||||||
|
action_required: true,
|
||||||
|
action_text: Some("Vote Now".to_string()),
|
||||||
|
action_url: Some("/governance".to_string()),
|
||||||
|
},
|
||||||
|
NotificationItem {
|
||||||
|
id: "3".to_string(),
|
||||||
|
title: "Payment Successful".to_string(),
|
||||||
|
message: "Monthly subscription payment of $50.00 processed successfully.".to_string(),
|
||||||
|
notification_type: NotificationType::Success,
|
||||||
|
timestamp: "3 days ago".to_string(),
|
||||||
|
is_read: true,
|
||||||
|
action_required: false,
|
||||||
|
action_text: None,
|
||||||
|
action_url: None,
|
||||||
|
},
|
||||||
|
NotificationItem {
|
||||||
|
id: "4".to_string(),
|
||||||
|
title: "Document Review Required".to_string(),
|
||||||
|
message: "Please review and sign the updated Terms of Service.".to_string(),
|
||||||
|
notification_type: NotificationType::Action,
|
||||||
|
timestamp: "1 week ago".to_string(),
|
||||||
|
is_read: true,
|
||||||
|
action_required: true,
|
||||||
|
action_text: Some("Review".to_string()),
|
||||||
|
action_url: Some("/contracts".to_string()),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
props.notifications.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let unread_count = notifications.iter().filter(|n| !n.is_read).count();
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<style>
|
||||||
|
{r#"
|
||||||
|
.inbox-card {
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
.inbox-card:hover {
|
||||||
|
border-color: #dee2e6;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||||
|
}
|
||||||
|
.notification-item {
|
||||||
|
border-radius: 8px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.notification-item:hover {
|
||||||
|
background-color: #f8f9fa !important;
|
||||||
|
}
|
||||||
|
.notification-item.unread {
|
||||||
|
border-left: 3px solid #0d6efd;
|
||||||
|
}
|
||||||
|
.notification-icon {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
.action-btn {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
background: white;
|
||||||
|
color: #495057;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
.action-btn:hover {
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-color: #adb5bd;
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
.action-btn.primary {
|
||||||
|
background: #0d6efd;
|
||||||
|
border-color: #0d6efd;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.action-btn.primary:hover {
|
||||||
|
background: #0b5ed7;
|
||||||
|
border-color: #0a58ca;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.bg-purple-subtle {
|
||||||
|
background-color: rgba(102, 16, 242, 0.1);
|
||||||
|
}
|
||||||
|
.text-purple {
|
||||||
|
color: #6610f2;
|
||||||
|
}
|
||||||
|
"#}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="inbox-card bg-white">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<div class="d-flex align-items-center justify-content-between mb-3">
|
||||||
|
<div class="d-flex align-items-center">
|
||||||
|
<i class="bi bi-inbox-fill text-primary me-2" style="font-size: 1.2rem;"></i>
|
||||||
|
<h5 class="mb-0 fw-semibold">{"Inbox"}</h5>
|
||||||
|
</div>
|
||||||
|
if unread_count > 0 {
|
||||||
|
<span class="badge bg-primary rounded-pill">{unread_count}</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex flex-column gap-3">
|
||||||
|
{for notifications.iter().take(4).map(|notification| {
|
||||||
|
html! {
|
||||||
|
<div class={classes!(
|
||||||
|
"notification-item",
|
||||||
|
"p-3",
|
||||||
|
(!notification.is_read).then(|| "unread")
|
||||||
|
)}>
|
||||||
|
<div class="d-flex align-items-start">
|
||||||
|
<div class={classes!(
|
||||||
|
"notification-icon",
|
||||||
|
"me-3",
|
||||||
|
"flex-shrink-0",
|
||||||
|
notification.notification_type.get_bg_color()
|
||||||
|
)}>
|
||||||
|
<i class={classes!(
|
||||||
|
"bi",
|
||||||
|
notification.notification_type.get_icon(),
|
||||||
|
notification.notification_type.get_color()
|
||||||
|
)}></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-grow-1 min-w-0">
|
||||||
|
<div class="d-flex align-items-start justify-content-between mb-1">
|
||||||
|
<h6 class={classes!(
|
||||||
|
"mb-0",
|
||||||
|
"text-truncate",
|
||||||
|
(!notification.is_read).then(|| "fw-semibold")
|
||||||
|
)} style="font-size: 0.9rem;">
|
||||||
|
{¬ification.title}
|
||||||
|
</h6>
|
||||||
|
<small class="text-muted ms-2 flex-shrink-0" style="font-size: 0.75rem;">
|
||||||
|
{¬ification.timestamp}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-muted mb-2 small" style="font-size: 0.8rem; line-height: 1.4;">
|
||||||
|
{¬ification.message}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
if let Some(action_text) = ¬ification.action_text {
|
||||||
|
<button class={classes!(
|
||||||
|
"action-btn",
|
||||||
|
notification.action_required.then(|| "primary")
|
||||||
|
)}>
|
||||||
|
{action_text}
|
||||||
|
if notification.action_required {
|
||||||
|
<i class="bi bi-arrow-right ms-1" style="font-size: 0.7rem;"></i>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
if notifications.len() > 4 {
|
||||||
|
<div class="text-center mt-3 pt-3 border-top">
|
||||||
|
<button class="btn btn-outline-primary btn-sm">
|
||||||
|
{"View All Notifications"}
|
||||||
|
<i class="bi bi-arrow-right ms-1"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,6 @@ pub fn sidebar(props: &SidebarProps) -> Html {
|
|||||||
AppView::Home,
|
AppView::Home,
|
||||||
AppView::Administration,
|
AppView::Administration,
|
||||||
AppView::PersonAdministration,
|
AppView::PersonAdministration,
|
||||||
AppView::Residence,
|
|
||||||
AppView::Accounting,
|
AppView::Accounting,
|
||||||
AppView::Contracts,
|
AppView::Contracts,
|
||||||
AppView::Governance,
|
AppView::Governance,
|
||||||
|
@ -8,6 +8,8 @@ pub mod toast;
|
|||||||
pub mod common;
|
pub mod common;
|
||||||
pub mod accounting;
|
pub mod accounting;
|
||||||
pub mod resident_landing_overlay;
|
pub mod resident_landing_overlay;
|
||||||
|
pub mod inbox;
|
||||||
|
pub mod residence_card;
|
||||||
|
|
||||||
pub use layout::*;
|
pub use layout::*;
|
||||||
pub use forms::*;
|
pub use forms::*;
|
||||||
@ -18,4 +20,6 @@ pub use entities::*;
|
|||||||
pub use toast::*;
|
pub use toast::*;
|
||||||
pub use common::*;
|
pub use common::*;
|
||||||
pub use accounting::*;
|
pub use accounting::*;
|
||||||
pub use resident_landing_overlay::*;
|
pub use resident_landing_overlay::*;
|
||||||
|
pub use inbox::*;
|
||||||
|
pub use residence_card::*;
|
145
platform/src/components/residence_card.rs
Normal file
145
platform/src/components/residence_card.rs
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct ResidenceCardProps {
|
||||||
|
pub user_name: String,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub email: Option<String>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub public_key: Option<String>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub resident_id: Option<String>,
|
||||||
|
#[prop_or_default]
|
||||||
|
pub status: ResidenceStatus,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub enum ResidenceStatus {
|
||||||
|
Active,
|
||||||
|
Pending,
|
||||||
|
Suspended,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResidenceStatus {
|
||||||
|
pub fn get_badge_class(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
ResidenceStatus::Active => "bg-success",
|
||||||
|
ResidenceStatus::Pending => "bg-warning",
|
||||||
|
ResidenceStatus::Suspended => "bg-danger",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_text(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
ResidenceStatus::Active => "ACTIVE",
|
||||||
|
ResidenceStatus::Pending => "PENDING",
|
||||||
|
ResidenceStatus::Suspended => "SUSPENDED",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ResidenceStatus {
|
||||||
|
fn default() -> Self {
|
||||||
|
ResidenceStatus::Active
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component(ResidenceCard)]
|
||||||
|
pub fn residence_card(props: &ResidenceCardProps) -> Html {
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<style>
|
||||||
|
{r#"
|
||||||
|
.residence-card-container {
|
||||||
|
perspective: 1000px;
|
||||||
|
}
|
||||||
|
.residence-card {
|
||||||
|
transform-style: preserve-3d;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
.residence-card:hover {
|
||||||
|
transform: rotateY(5deg) rotateX(5deg);
|
||||||
|
}
|
||||||
|
"#}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="residence-card-container d-flex align-items-center justify-content-center">
|
||||||
|
<div class="residence-card">
|
||||||
|
<div class="card border-0 shadow-lg" style="width: 350px; background: white; border-radius: 15px;">
|
||||||
|
// Header with Zanzibar flag gradient
|
||||||
|
<div style="background: linear-gradient(135deg, #0099FF 0%, #00CC66 100%); height: 80px; border-radius: 15px 15px 0 0; position: relative;">
|
||||||
|
<div class="position-absolute top-0 start-0 w-100 h-100 d-flex align-items-center justify-content-between px-4">
|
||||||
|
<div>
|
||||||
|
<h6 class="mb-0 text-white" style="font-size: 0.9rem; font-weight: 600;">{"DIGITAL RESIDENT"}</h6>
|
||||||
|
<small class="text-white" style="opacity: 0.9; font-size: 0.75rem;">{"Zanzibar Digital Freezone"}</small>
|
||||||
|
</div>
|
||||||
|
<i class="bi bi-shield-check-fill text-white" style="font-size: 1.5rem; opacity: 0.9;"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
// Card body with white background
|
||||||
|
<div class="card-body p-4" style="background: white; border-radius: 0 0 15px 15px;">
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="text-muted small" style="font-size: 0.7rem; font-weight: 500; letter-spacing: 0.5px;">{"FULL NAME"}</div>
|
||||||
|
<div class="h5 mb-0 text-dark" style="font-weight: 600;">
|
||||||
|
{&props.user_name}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="text-muted small" style="font-size: 0.7rem; font-weight: 500; letter-spacing: 0.5px;">{"EMAIL"}</div>
|
||||||
|
<div class="text-dark" style="font-size: 0.9rem;">
|
||||||
|
{props.email.as_ref().unwrap_or(&"resident@zanzibar-freezone.com".to_string())}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="text-muted small d-flex align-items-center" style="font-size: 0.7rem; font-weight: 500; letter-spacing: 0.5px;">
|
||||||
|
<i class="bi bi-key me-1" style="font-size: 0.8rem;"></i>
|
||||||
|
{"PUBLIC KEY"}
|
||||||
|
</div>
|
||||||
|
<div class="text-dark" style="font-size: 0.7rem; font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace; word-break: break-all; line-height: 1.3;">
|
||||||
|
{if let Some(public_key) = &props.public_key {
|
||||||
|
format!("{}...", &public_key[..std::cmp::min(24, public_key.len())])
|
||||||
|
} else {
|
||||||
|
"zdf1qxy2mlyjkjkpskpsw9fxtpugs450add72nyktmzqau...".to_string()
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="text-muted small" style="font-size: 0.7rem; font-weight: 500; letter-spacing: 0.5px;">{"RESIDENT SINCE"}</div>
|
||||||
|
<div class="text-dark" style="font-size: 0.8rem;">
|
||||||
|
{"2025"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-end mb-3">
|
||||||
|
<div>
|
||||||
|
<div class="text-muted small" style="font-size: 0.7rem; font-weight: 500; letter-spacing: 0.5px;">{"RESIDENT ID"}</div>
|
||||||
|
<div class="text-dark" style="font-weight: 600;">
|
||||||
|
{props.resident_id.as_ref().unwrap_or(&"ZDF-2025-****".to_string())}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-end">
|
||||||
|
<div class="text-muted small" style="font-size: 0.7rem; font-weight: 500; letter-spacing: 0.5px;">{"STATUS"}</div>
|
||||||
|
<div class={classes!("badge", props.status.get_badge_class())} style="color: white; font-weight: 500;">
|
||||||
|
{props.status.get_text()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
// QR Code at bottom
|
||||||
|
<div class="text-center border-top pt-3" style="border-color: #e9ecef !important;">
|
||||||
|
<div class="d-inline-block p-2 rounded" style="background: #f8f9fa;">
|
||||||
|
<div style="width: 60px; height: 60px; background: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjYwIiBoZWlnaHQ9IjYwIiBmaWxsPSJ3aGl0ZSIvPgo8cmVjdCB4PSI0IiB5PSI0IiB3aWR0aD0iMTIiIGhlaWdodD0iMTIiIGZpbGw9ImJsYWNrIi8+CjxyZWN0IHg9IjIwIiB5PSI0IiB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBmaWxsPSJibGFjayIvPgo8cmVjdCB4PSIyOCIgeT0iNCIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0iYmxhY2siLz4KPHJlY3QgeD0iNDQiIHk9IjQiIHdpZHRoPSIxMiIgaGVpZ2h0PSIxMiIgZmlsbD0iYmxhY2siLz4KPHJlY3QgeD0iOCIgeT0iOCIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0id2hpdGUiLz4KPHJlY3QgeD0iNDgiIHk9IjgiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiIGZpbGw9IndoaXRlIi8+CjxyZWN0IHg9IjIwIiB5PSIxMiIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0iYmxhY2siLz4KPHJlY3QgeD0iMzYiIHk9IjEyIiB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBmaWxsPSJibGFjayIvPgo8cmVjdCB4PSI0IiB5PSIyMCIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0iYmxhY2siLz4KPHJlY3QgeD0iMTIiIHk9IjIwIiB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBmaWxsPSJibGFjayIvPgo8cmVjdCB4PSIyMCIgeT0iMjAiIHdpZHRoPSI4IiBoZWlnaHQ9IjgiIGZpbGw9ImJsYWNrIi8+CjxyZWN0IHg9IjM2IiB5PSIyMCIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0iYmxhY2siLz4KPHJlY3QgeD0iNDQiIHk9IjIwIiB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBmaWxsPSJibGFjayIvPgo8cmVjdCB4PSI1MiIgeT0iMjAiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiIGZpbGw9ImJsYWNrIi8+CjxyZWN0IHg9IjI0IiB5PSIyNCIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0id2hpdGUiLz4KPHJlY3QgeD0iNCIgeT0iMjgiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiIGZpbGw9ImJsYWNrIi8+CjxyZWN0IHg9IjEyIiB5PSIyOCIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0iYmxhY2siLz4KPHJlY3QgeD0iMzYiIHk9IjI4IiB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBmaWxsPSJibGFjayIvPgo8cmVjdCB4PSI0NCIgeT0iMjgiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiIGZpbGw9ImJsYWNrIi8+CjxyZWN0IHg9IjUyIiB5PSIyOCIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0iYmxhY2siLz4KPHJlY3QgeD0iNCIgeT0iMzYiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiIGZpbGw9ImJsYWNrIi8+CjxyZWN0IHg9IjEyIiB5PSIzNiIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0iYmxhY2siLz4KPHJlY3QgeD0iMjAiIHk9IjM2IiB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBmaWxsPSJibGFjayIvPgo8cmVjdCB4PSIyOCIgeT0iMzYiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiIGZpbGw9ImJsYWNrIi8+CjxyZWN0IHg9IjM2IiB5PSIzNiIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0iYmxhY2siLz4KPHJlY3QgeD0iNDQiIHk9IjM2IiB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBmaWxsPSJibGFjayIvPgo8cmVjdCB4PSI1MiIgeT0iMzYiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiIGZpbGw9ImJsYWNrIi8+CjxyZWN0IHg9IjQiIHk9IjQ0IiB3aWR0aD0iMTIiIGhlaWdodD0iMTIiIGZpbGw9ImJsYWNrIi8+CjxyZWN0IHg9IjIwIiB5PSI0NCIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0iYmxhY2siLz4KPHJlY3QgeD0iMjgiIHk9IjQ0IiB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBmaWxsPSJibGFjayIvPgo8cmVjdCB4PSI0NCIgeT0iNDQiIHdpZHRoPSIxMiIgaGVpZ2h0PSIxMiIgZmlsbD0iYmxhY2siLz4KPHJlY3QgeD0iOCIgeT0iNDgiIHdpZHRoPSI0IiBoZWlnaHQ9IjQiIGZpbGw9IndoaXRlIi8+CjxyZWN0IHg9IjIwIiB5PSI1MiIgd2lkdGg9IjQiIGhlaWdodD0iNCIgZmlsbD0iYmxhY2siLz4KPHJlY3QgeD0iNDgiIHk9IjQ4IiB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBmaWxsPSJ3aGl0ZSIvPgo8L3N2Zz4K') no-repeat center; background-size: contain;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted small mt-2" style="font-size: 0.7rem;">{"Scan to verify"}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,8 @@ pub struct ViewComponentProps {
|
|||||||
pub empty_state: Option<(String, String, String, Option<(String, String)>, Option<(String, String)>)>, // (icon, title, description, primary_action, secondary_action)
|
pub empty_state: Option<(String, String, String, Option<(String, String)>, Option<(String, String)>)>, // (icon, title, description, primary_action, secondary_action)
|
||||||
#[prop_or_default]
|
#[prop_or_default]
|
||||||
pub children: Children, // Main content when no tabs
|
pub children: Children, // Main content when no tabs
|
||||||
|
#[prop_or_default]
|
||||||
|
pub use_modern_header: bool, // Use modern header style without card wrapper
|
||||||
}
|
}
|
||||||
|
|
||||||
#[function_component(ViewComponent)]
|
#[function_component(ViewComponent)]
|
||||||
@ -40,7 +42,8 @@ pub fn view_component(props: &ViewComponentProps) -> Html {
|
|||||||
};
|
};
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div class="container-fluid">
|
<div class="container-fluid" style="max-width: 1100px;">
|
||||||
|
<div class="px-3 px-md-4 px-lg-5 px-xl-6">
|
||||||
// Breadcrumbs (if provided)
|
// Breadcrumbs (if provided)
|
||||||
if let Some(breadcrumbs) = &props.breadcrumbs {
|
if let Some(breadcrumbs) = &props.breadcrumbs {
|
||||||
<ol class="breadcrumb mb-3">
|
<ol class="breadcrumb mb-3">
|
||||||
@ -59,72 +62,144 @@ pub fn view_component(props: &ViewComponentProps) -> Html {
|
|||||||
</ol>
|
</ol>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Page Header in Card (with integrated tabs if provided)
|
if props.use_modern_header {
|
||||||
if props.title.is_some() || props.description.is_some() || props.actions.is_some() || props.tabs.is_some() {
|
// Modern header style without card wrapper
|
||||||
<div class="row mb-4">
|
if props.title.is_some() || props.description.is_some() || props.actions.is_some() {
|
||||||
<div class="col-12">
|
<div class="d-flex justify-content-between align-items-end mb-4">
|
||||||
<div class="card">
|
// Left side: Title and description
|
||||||
<div class="card-body">
|
<div>
|
||||||
<div class="d-flex justify-content-between align-items-end">
|
if let Some(title) = &props.title {
|
||||||
// Left side: Title and description
|
<h2 class="mb-1 fw-bold">{title}</h2>
|
||||||
<div class="flex-grow-1">
|
}
|
||||||
if let Some(title) = &props.title {
|
if let Some(description) = &props.description {
|
||||||
<h2 class="mb-1">{title}</h2>
|
<p class="text-muted mb-0">{description}</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
// Right side: Actions
|
||||||
|
if let Some(actions) = &props.actions {
|
||||||
|
<div>
|
||||||
|
{actions.clone()}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modern tabs navigation (if provided)
|
||||||
|
if let Some(tabs) = &props.tabs {
|
||||||
|
<div class="mb-0">
|
||||||
|
<ul class="nav nav-tabs border-bottom-0" role="tablist">
|
||||||
|
{for tabs.keys().map(|tab_name| {
|
||||||
|
let is_active = *active_tab == *tab_name;
|
||||||
|
let tab_name_clone = tab_name.clone();
|
||||||
|
let on_click = {
|
||||||
|
let on_tab_click = on_tab_click.clone();
|
||||||
|
let tab_name = tab_name.clone();
|
||||||
|
Callback::from(move |e: MouseEvent| {
|
||||||
|
e.prevent_default();
|
||||||
|
on_tab_click.emit(tab_name.clone());
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button
|
||||||
|
class={classes!(
|
||||||
|
"nav-link",
|
||||||
|
"px-3",
|
||||||
|
"py-2",
|
||||||
|
"small",
|
||||||
|
"border",
|
||||||
|
"border-bottom-0",
|
||||||
|
"bg-light",
|
||||||
|
"text-muted",
|
||||||
|
if is_active {
|
||||||
|
"active bg-white text-dark border-primary border-bottom-0"
|
||||||
|
} else {
|
||||||
|
"border-light"
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
onclick={on_click}
|
||||||
|
style={if is_active { "margin-bottom: -1px; z-index: 1; position: relative;" } else { "" }}
|
||||||
|
>
|
||||||
|
{tab_name}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Original header style with card wrapper
|
||||||
|
if props.title.is_some() || props.description.is_some() || props.actions.is_some() || props.tabs.is_some() {
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex justify-content-between align-items-end">
|
||||||
|
// Left side: Title and description
|
||||||
|
<div class="flex-grow-1">
|
||||||
|
if let Some(title) = &props.title {
|
||||||
|
<h2 class="mb-1">{title}</h2>
|
||||||
|
}
|
||||||
|
if let Some(description) = &props.description {
|
||||||
|
<p class="text-muted mb-0">{description}</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
// Center: Tabs navigation (if provided)
|
||||||
|
if let Some(tabs) = &props.tabs {
|
||||||
|
<div class="flex-grow-1 d-flex justify-content-right">
|
||||||
|
<ul class="nav nav-tabs border-0" role="tablist">
|
||||||
|
{for tabs.keys().map(|tab_name| {
|
||||||
|
let is_active = *active_tab == *tab_name;
|
||||||
|
let tab_name_clone = tab_name.clone();
|
||||||
|
let on_click = {
|
||||||
|
let on_tab_click = on_tab_click.clone();
|
||||||
|
let tab_name = tab_name.clone();
|
||||||
|
Callback::from(move |e: MouseEvent| {
|
||||||
|
e.prevent_default();
|
||||||
|
on_tab_click.emit(tab_name.clone());
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button
|
||||||
|
class={classes!("nav-link", "px-4", "py-2", is_active.then(|| "active"))}
|
||||||
|
type="button"
|
||||||
|
role="tab"
|
||||||
|
onclick={on_click}
|
||||||
|
>
|
||||||
|
{tab_name}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
if let Some(description) = &props.description {
|
|
||||||
<p class="text-muted mb-0">{description}</p>
|
// Right side: Actions
|
||||||
|
if let Some(actions) = &props.actions {
|
||||||
|
<div>
|
||||||
|
{actions.clone()}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
// Center: Tabs navigation (if provided)
|
|
||||||
if let Some(tabs) = &props.tabs {
|
|
||||||
<div class="flex-grow-1 d-flex justify-content-right">
|
|
||||||
<ul class="nav nav-tabs border-0" role="tablist">
|
|
||||||
{for tabs.keys().map(|tab_name| {
|
|
||||||
let is_active = *active_tab == *tab_name;
|
|
||||||
let tab_name_clone = tab_name.clone();
|
|
||||||
let on_click = {
|
|
||||||
let on_tab_click = on_tab_click.clone();
|
|
||||||
let tab_name = tab_name.clone();
|
|
||||||
Callback::from(move |e: MouseEvent| {
|
|
||||||
e.prevent_default();
|
|
||||||
on_tab_click.emit(tab_name.clone());
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
html! {
|
|
||||||
<li class="nav-item" role="presentation">
|
|
||||||
<button
|
|
||||||
class={classes!("nav-link", "px-4", "py-2", is_active.then(|| "active"))}
|
|
||||||
type="button"
|
|
||||||
role="tab"
|
|
||||||
onclick={on_click}
|
|
||||||
>
|
|
||||||
{tab_name}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
// Right side: Actions
|
|
||||||
if let Some(actions) = &props.actions {
|
|
||||||
<div>
|
|
||||||
{actions.clone()}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tab Content (if tabs are provided)
|
// Tab Content (if tabs are provided)
|
||||||
if let Some(tabs) = &props.tabs {
|
if let Some(tabs) = &props.tabs {
|
||||||
<div class="tab-content">
|
<div class="tab-content border border-top-0 rounded-bottom bg-white p-4">
|
||||||
{for tabs.iter().map(|(tab_name, content)| {
|
{for tabs.iter().map(|(tab_name, content)| {
|
||||||
let is_active = *active_tab == *tab_name;
|
let is_active = *active_tab == *tab_name;
|
||||||
html! {
|
html! {
|
||||||
@ -147,6 +222,7 @@ pub fn view_component(props: &ViewComponentProps) -> Html {
|
|||||||
// No tabs, render children directly
|
// No tabs, render children directly
|
||||||
{for props.children.iter()}
|
{for props.children.iter()}
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -254,12 +254,30 @@ pub fn accounting_view(props: &AccountingViewProps) -> Html {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
ViewContext::Person => {
|
ViewContext::Person => {
|
||||||
// For personal context, show simplified version
|
// Show same functionality as business context
|
||||||
tabs.insert("Income Tracking".to_string(), html! {
|
// Overview Tab
|
||||||
<div class="alert alert-info">
|
tabs.insert("Overview".to_string(), html! {
|
||||||
<i class="bi bi-info-circle me-2"></i>
|
<OverviewTab state={state.clone()} />
|
||||||
{"Personal accounting features coming soon. Switch to Business context for full accounting functionality."}
|
});
|
||||||
</div>
|
|
||||||
|
// Revenue Tab
|
||||||
|
tabs.insert("Revenue".to_string(), html! {
|
||||||
|
<RevenueTab state={state.clone()} />
|
||||||
|
});
|
||||||
|
|
||||||
|
// Expenses Tab
|
||||||
|
tabs.insert("Expenses".to_string(), html! {
|
||||||
|
<ExpensesTab state={state.clone()} />
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tax Tab
|
||||||
|
tabs.insert("Tax".to_string(), html! {
|
||||||
|
<TaxTab state={state.clone()} />
|
||||||
|
});
|
||||||
|
|
||||||
|
// Financial Reports Tab
|
||||||
|
tabs.insert("Financial Reports".to_string(), html! {
|
||||||
|
<FinancialReportsTab state={state.clone()} />
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -274,10 +292,8 @@ pub fn accounting_view(props: &AccountingViewProps) -> Html {
|
|||||||
title={Some(title.to_string())}
|
title={Some(title.to_string())}
|
||||||
description={Some(description.to_string())}
|
description={Some(description.to_string())}
|
||||||
tabs={Some(tabs)}
|
tabs={Some(tabs)}
|
||||||
default_tab={match context {
|
default_tab={Some("Overview".to_string())}
|
||||||
ViewContext::Business => Some("Overview".to_string()),
|
use_modern_header={true}
|
||||||
ViewContext::Person => Some("Income Tracking".to_string()),
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -164,6 +164,7 @@ impl Component for CompaniesView {
|
|||||||
<ViewComponent
|
<ViewComponent
|
||||||
title={Some("Registration Successful".to_string())}
|
title={Some("Registration Successful".to_string())}
|
||||||
description={Some("Your company registration has been completed successfully".to_string())}
|
description={Some("Your company registration has been completed successfully".to_string())}
|
||||||
|
use_modern_header={true}
|
||||||
>
|
>
|
||||||
<RegistrationWizard
|
<RegistrationWizard
|
||||||
on_registration_complete={link.callback(CompaniesViewMsg::RegistrationComplete)}
|
on_registration_complete={link.callback(CompaniesViewMsg::RegistrationComplete)}
|
||||||
@ -182,6 +183,7 @@ impl Component for CompaniesView {
|
|||||||
<ViewComponent
|
<ViewComponent
|
||||||
title={Some("Register New Company".to_string())}
|
title={Some("Register New Company".to_string())}
|
||||||
description={Some("Complete the registration process to create your new company".to_string())}
|
description={Some("Complete the registration process to create your new company".to_string())}
|
||||||
|
use_modern_header={true}
|
||||||
>
|
>
|
||||||
<RegistrationWizard
|
<RegistrationWizard
|
||||||
on_registration_complete={link.callback(CompaniesViewMsg::RegistrationComplete)}
|
on_registration_complete={link.callback(CompaniesViewMsg::RegistrationComplete)}
|
||||||
@ -200,6 +202,7 @@ impl Component for CompaniesView {
|
|||||||
<ViewComponent
|
<ViewComponent
|
||||||
title={Some("Companies".to_string())}
|
title={Some("Companies".to_string())}
|
||||||
description={Some("Manage your companies and registrations".to_string())}
|
description={Some("Manage your companies and registrations".to_string())}
|
||||||
|
use_modern_header={true}
|
||||||
>
|
>
|
||||||
{self.render_companies_content(ctx)}
|
{self.render_companies_content(ctx)}
|
||||||
</ViewComponent>
|
</ViewComponent>
|
||||||
@ -258,24 +261,27 @@ impl CompaniesView {
|
|||||||
let link = ctx.link();
|
let link = ctx.link();
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div class="card">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-body p-4">
|
||||||
<div>
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
<h5 class="mb-0">
|
<div class="d-flex align-items-center">
|
||||||
<i class="bi bi-building me-2"></i>{"Companies & Registrations"}
|
<div class="bg-primary bg-opacity-10 rounded-3 p-2 me-3">
|
||||||
</h5>
|
<i class="bi bi-building text-primary fs-5"></i>
|
||||||
<small class="text-muted">
|
</div>
|
||||||
{format!("{} companies, {} pending registrations", self.companies.len(), self.registrations.len())}
|
<div>
|
||||||
</small>
|
<h5 class="mb-0">{"Companies & Registrations"}</h5>
|
||||||
|
<small class="text-muted">
|
||||||
|
{format!("{} companies, {} pending registrations", self.companies.len(), self.registrations.len())}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="btn btn-success"
|
||||||
|
onclick={link.callback(|_| CompaniesViewMsg::StartNewRegistration)}
|
||||||
|
>
|
||||||
|
<i class="bi bi-plus-circle me-2"></i>{"New Registration"}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<button
|
|
||||||
class="btn btn-success"
|
|
||||||
onclick={link.callback(|_| CompaniesViewMsg::StartNewRegistration)}
|
|
||||||
>
|
|
||||||
<i class="bi bi-plus-circle me-2"></i>{"New Registration"}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="card-body p-0">
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover align-middle mb-0">
|
<table class="table table-hover align-middle mb-0">
|
||||||
<thead class="table-light">
|
<thead class="table-light">
|
||||||
|
@ -170,10 +170,11 @@ impl Component for ContractsViewComponent {
|
|||||||
|
|
||||||
html! {
|
html! {
|
||||||
<ViewComponent
|
<ViewComponent
|
||||||
title={title.to_string()}
|
title={Some(title.to_string())}
|
||||||
description={description.to_string()}
|
description={Some(description.to_string())}
|
||||||
tabs={tabs}
|
tabs={Some(tabs)}
|
||||||
default_tab={"Contracts".to_string()}
|
default_tab={Some("Contracts".to_string())}
|
||||||
|
use_modern_header={true}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -296,11 +297,14 @@ impl ContractsViewComponent {
|
|||||||
// Filters Section
|
// Filters Section
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header">
|
<div class="card-body p-4">
|
||||||
<h5 class="mb-0">{"Filters"}</h5>
|
<div class="d-flex align-items-center mb-3">
|
||||||
</div>
|
<div class="bg-primary bg-opacity-10 rounded-3 p-2 me-3">
|
||||||
<div class="card-body">
|
<i class="bi bi-funnel text-primary fs-5"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="mb-0">{"Filters"}</h5>
|
||||||
|
</div>
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<label for="status" class="form-label">{"Status"}</label>
|
<label for="status" class="form-label">{"Status"}</label>
|
||||||
@ -344,11 +348,14 @@ impl ContractsViewComponent {
|
|||||||
// Contracts Table
|
// Contracts Table
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="card">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header">
|
<div class="card-body p-4">
|
||||||
<h5 class="mb-0">{"Contracts"}</h5>
|
<div class="d-flex align-items-center mb-3">
|
||||||
</div>
|
<div class="bg-success bg-opacity-10 rounded-3 p-2 me-3">
|
||||||
<div class="card-body">
|
<i class="bi bi-file-earmark-text text-success fs-5"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="mb-0">{"Contracts"}</h5>
|
||||||
|
</div>
|
||||||
{self.render_contracts_table(_ctx)}
|
{self.render_contracts_table(_ctx)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -442,11 +449,14 @@ impl ContractsViewComponent {
|
|||||||
html! {
|
html! {
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-8">
|
<div class="col-lg-8">
|
||||||
<div class="card">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header">
|
<div class="card-body p-4">
|
||||||
<h5 class="mb-0">{"Contract Details"}</h5>
|
<div class="d-flex align-items-center mb-4">
|
||||||
</div>
|
<div class="bg-primary bg-opacity-10 rounded-3 p-2 me-3">
|
||||||
<div class="card-body">
|
<i class="bi bi-file-earmark-plus text-primary fs-5"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="mb-0">{"Contract Details"}</h5>
|
||||||
|
</div>
|
||||||
<form>
|
<form>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="title" class="form-label">
|
<label for="title" class="form-label">
|
||||||
@ -531,11 +541,14 @@ Payment will be made according to the following schedule:
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-lg-4">
|
<div class="col-lg-4">
|
||||||
<div class="card mb-4">
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
<div class="card-header">
|
<div class="card-body p-4">
|
||||||
<h5 class="mb-0">{"Tips"}</h5>
|
<div class="d-flex align-items-center mb-3">
|
||||||
</div>
|
<div class="bg-info bg-opacity-10 rounded-3 p-2 me-3">
|
||||||
<div class="card-body">
|
<i class="bi bi-lightbulb text-info fs-5"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="mb-0">{"Tips"}</h5>
|
||||||
|
</div>
|
||||||
<p>{"Creating a new contract is just the first step. After creating the contract, you'll be able to:"}</p>
|
<p>{"Creating a new contract is just the first step. After creating the contract, you'll be able to:"}</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>{"Add signers who need to approve the contract"}</li>
|
<li>{"Add signers who need to approve the contract"}</li>
|
||||||
@ -547,11 +560,14 @@ Payment will be made according to the following schedule:
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header">
|
<div class="card-body p-4">
|
||||||
<h5 class="mb-0">{"Contract Templates"}</h5>
|
<div class="d-flex align-items-center mb-3">
|
||||||
</div>
|
<div class="bg-warning bg-opacity-10 rounded-3 p-2 me-3">
|
||||||
<div class="card-body">
|
<i class="bi bi-file-earmark-code text-warning fs-5"></i>
|
||||||
|
</div>
|
||||||
|
<h5 class="mb-0">{"Contract Templates"}</h5>
|
||||||
|
</div>
|
||||||
<p>{"You can use one of our pre-defined templates to get started quickly:"}</p>
|
<p>{"You can use one of our pre-defined templates to get started quickly:"}</p>
|
||||||
<div class="list-group">
|
<div class="list-group">
|
||||||
<button type="button" class="list-group-item list-group-item-action">
|
<button type="button" class="list-group-item list-group-item-action">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use crate::components::FeatureCard;
|
use crate::components::{Inbox, ResidenceCard, ResidenceStatus};
|
||||||
use crate::routing::ViewContext;
|
use crate::routing::ViewContext;
|
||||||
|
|
||||||
#[derive(Properties, PartialEq)]
|
#[derive(Properties, PartialEq)]
|
||||||
@ -9,74 +9,125 @@ pub struct HomeViewProps {
|
|||||||
|
|
||||||
#[function_component(HomeView)]
|
#[function_component(HomeView)]
|
||||||
pub fn home_view(props: &HomeViewProps) -> Html {
|
pub fn home_view(props: &HomeViewProps) -> Html {
|
||||||
|
// Mock user data - in a real app this would come from authentication/user context
|
||||||
|
let user_name = "Timur Gordon".to_string();
|
||||||
|
let user_email = Some("timur@example.com".to_string());
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div class="row">
|
<>
|
||||||
<div class="col-md-12">
|
<style>
|
||||||
<div class="card">
|
{r#"
|
||||||
<div class="card-body">
|
.welcome-section {
|
||||||
<h1 class="card-title text-center mb-4">{"Zanzibar Digital Freezone"}</h1>
|
background: linear-gradient(135deg, rgba(0,153,255,0.05) 0%, rgba(0,204,102,0.05) 100%);
|
||||||
<p class="card-text text-center lead mb-5">{"Convenience, Safety and Privacy"}</p>
|
border-radius: 16px;
|
||||||
|
border: 1px solid rgba(0,153,255,0.1);
|
||||||
<div class="row g-3 mb-4">
|
}
|
||||||
// Left Column (3 items)
|
.greeting-card {
|
||||||
<div class="col-md-6">
|
border: 1px solid #e9ecef;
|
||||||
// Card 1: Frictionless Collaboration
|
border-radius: 12px;
|
||||||
<FeatureCard
|
transition: all 0.2s ease;
|
||||||
title="Frictionless Collaboration"
|
}
|
||||||
description="Direct communication and transactions between individuals and organizations, making processes efficient and cost-effective."
|
.greeting-card:hover {
|
||||||
icon="bi-people-fill"
|
border-color: #dee2e6;
|
||||||
color_variant="primary"
|
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||||
/>
|
}
|
||||||
|
.time-badge {
|
||||||
// Card 2: Frictionless Banking
|
background: linear-gradient(135deg, #0099FF 0%, #00CC66 100%);
|
||||||
<FeatureCard
|
color: white;
|
||||||
title="Frictionless Banking"
|
padding: 4px 12px;
|
||||||
description="Simplified financial transactions without the complications and fees of traditional banking systems."
|
border-radius: 20px;
|
||||||
icon="bi-currency-exchange"
|
font-size: 0.8rem;
|
||||||
color_variant="success"
|
font-weight: 500;
|
||||||
/>
|
}
|
||||||
|
.stats-item {
|
||||||
// Card 3: Tax Efficiency
|
text-align: center;
|
||||||
<FeatureCard
|
padding: 1rem;
|
||||||
title="Tax Efficiency"
|
border-radius: 8px;
|
||||||
description="Lower taxes making business operations more profitable and competitive in the global market."
|
background: #f8f9fa;
|
||||||
icon="bi-graph-up-arrow"
|
transition: all 0.2s ease;
|
||||||
color_variant="info"
|
}
|
||||||
/>
|
.stats-item:hover {
|
||||||
|
background: #e9ecef;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
.stats-number {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #0d6efd;
|
||||||
|
}
|
||||||
|
.stats-label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #6c757d;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
"#}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="container-fluid py-4 px-3 px-md-4 px-lg-5 px-xl-6">
|
||||||
|
<div class="row g-4">
|
||||||
|
// Left Column: Greeting and Inbox
|
||||||
|
<div class="col-lg-6">
|
||||||
|
// Welcome Section
|
||||||
|
<div class="welcome-section p-4 mb-4">
|
||||||
|
<div class="d-flex align-items-center justify-content-between mb-3">
|
||||||
|
<div>
|
||||||
|
<h1 class="h3 mb-1 fw-bold text-dark">
|
||||||
|
{"Hello, "}{&user_name}{"! 👋"}
|
||||||
|
</h1>
|
||||||
|
<p class="text-muted mb-0">
|
||||||
|
{"Welcome back to your Digital Freezone dashboard"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
// Right Column (2 items)
|
// Quick Actions
|
||||||
<div class="col-md-6">
|
<div class="row g-3 mb-3">
|
||||||
// Card 4: Global Ecommerce
|
<div class="col-4">
|
||||||
<FeatureCard
|
<a href="/companies/register" class="text-decoration-none">
|
||||||
title="Global Ecommerce"
|
<div class="stats-item">
|
||||||
description="Easily expand your business globally with streamlined operations and tools to reach customers worldwide."
|
<i class="bi bi-building-add text-primary mb-2" style="font-size: 1.5rem;"></i>
|
||||||
icon="bi-globe"
|
<div class="stats-label">{"Register Company"}</div>
|
||||||
color_variant="warning"
|
</div>
|
||||||
/>
|
</a>
|
||||||
|
</div>
|
||||||
// Card 5: Clear Regulations
|
<div class="col-4">
|
||||||
<FeatureCard
|
<a href="/governance" class="text-decoration-none">
|
||||||
title="Clear Regulations"
|
<div class="stats-item">
|
||||||
description="Clear regulations and efficient dispute resolution mechanisms providing a stable business environment."
|
<i class="bi bi-hand-thumbs-up text-success mb-2" style="font-size: 1.5rem;"></i>
|
||||||
icon="bi-shield-check"
|
<div class="stats-label">{"Vote on Proposals"}</div>
|
||||||
color_variant="danger"
|
</div>
|
||||||
/>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<a href="/treasury" class="text-decoration-none">
|
||||||
|
<div class="stats-item">
|
||||||
|
<i class="bi bi-wallet2 text-info mb-2" style="font-size: 1.5rem;"></i>
|
||||||
|
<div class="stats-label">{"Manage Wallet"}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center">
|
// Inbox Component
|
||||||
<a
|
<Inbox />
|
||||||
href="https://info.ourworld.tf/zdfz"
|
</div>
|
||||||
target="_blank"
|
|
||||||
class="btn btn-primary btn-lg"
|
// Right Column: Residence Card
|
||||||
>
|
<div class="col-lg-6">
|
||||||
{"Learn More"}
|
<div class="d-flex align-items-center justify-content-center h-100">
|
||||||
</a>
|
<ResidenceCard
|
||||||
|
user_name={user_name}
|
||||||
|
email={user_email}
|
||||||
|
public_key={Some("zdf1qxy2mlyjkjkpskpsw9fxtpugs450add72nyktmzqau...".to_string())}
|
||||||
|
resident_id={Some("ZDF-2025-0001".to_string())}
|
||||||
|
status={ResidenceStatus::Active}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</>
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -273,29 +273,33 @@ pub fn person_administration_view(props: &PersonAdministrationViewProps) -> Html
|
|||||||
|
|
||||||
// Account Settings Tab (Person-specific)
|
// Account Settings Tab (Person-specific)
|
||||||
tabs.insert("Account Settings".to_string(), html! {
|
tabs.insert("Account Settings".to_string(), html! {
|
||||||
<div class="card">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header">
|
<div class="card-body p-4">
|
||||||
<h5 class="mb-0">
|
<div class="d-flex align-items-center mb-4">
|
||||||
<i class="bi bi-person-gear me-2"></i>
|
<div class="bg-primary bg-opacity-10 rounded-3 p-3 me-3">
|
||||||
{"Personal Account Settings"}
|
<i class="bi bi-person-gear text-primary fs-4"></i>
|
||||||
</h5>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div class="card-body">
|
<h5 class="mb-1">{"Personal Account Settings"}</h5>
|
||||||
|
<p class="text-muted mb-0">{"Manage your personal information and preferences"}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label class="form-label">{"Full Name"}</label>
|
<label class="form-label fw-medium">{"Full Name"}</label>
|
||||||
<input type="text" class="form-control" value="Timur Gordon" />
|
<input type="text" class="form-control" value="Timur Gordon" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label class="form-label">{"Email Address"}</label>
|
<label class="form-label fw-medium">{"Email Address"}</label>
|
||||||
<input type="email" class="form-control" value="john.doe@example.com" />
|
<input type="email" class="form-control" value="john.doe@example.com" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label class="form-label">{"Phone Number"}</label>
|
<label class="form-label fw-medium">{"Phone Number"}</label>
|
||||||
<input type="tel" class="form-control" value="+1 (555) 123-4567" />
|
<input type="tel" class="form-control" value="+1 (555) 123-4567" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
<label class="form-label">{"Preferred Language"}</label>
|
<label class="form-label fw-medium">{"Preferred Language"}</label>
|
||||||
<select class="form-select">
|
<select class="form-select">
|
||||||
<option selected=true>{"English"}</option>
|
<option selected=true>{"English"}</option>
|
||||||
<option>{"French"}</option>
|
<option>{"French"}</option>
|
||||||
@ -304,7 +308,7 @@ pub fn person_administration_view(props: &PersonAdministrationViewProps) -> Html
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 mb-3">
|
<div class="col-12 mb-3">
|
||||||
<label class="form-label">{"Time Zone"}</label>
|
<label class="form-label fw-medium">{"Time Zone"}</label>
|
||||||
<select class="form-select">
|
<select class="form-select">
|
||||||
<option selected=true>{"UTC+00:00 (GMT)"}</option>
|
<option selected=true>{"UTC+00:00 (GMT)"}</option>
|
||||||
<option>{"UTC-05:00 (EST)"}</option>
|
<option>{"UTC-05:00 (EST)"}</option>
|
||||||
@ -313,7 +317,7 @@ pub fn person_administration_view(props: &PersonAdministrationViewProps) -> Html
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4 pt-3 border-top">
|
||||||
<button class="btn btn-primary me-2">{"Save Changes"}</button>
|
<button class="btn btn-primary me-2">{"Save Changes"}</button>
|
||||||
<button class="btn btn-outline-secondary">{"Reset"}</button>
|
<button class="btn btn-outline-secondary">{"Reset"}</button>
|
||||||
</div>
|
</div>
|
||||||
@ -323,52 +327,56 @@ pub fn person_administration_view(props: &PersonAdministrationViewProps) -> Html
|
|||||||
|
|
||||||
// Privacy & Security Tab
|
// Privacy & Security Tab
|
||||||
tabs.insert("Privacy & Security".to_string(), html! {
|
tabs.insert("Privacy & Security".to_string(), html! {
|
||||||
<div class="card">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header">
|
<div class="card-body p-4">
|
||||||
<h5 class="mb-0">
|
<div class="d-flex align-items-center mb-4">
|
||||||
<i class="bi bi-shield-lock me-2"></i>
|
<div class="bg-success bg-opacity-10 rounded-3 p-3 me-3">
|
||||||
{"Privacy & Security Settings"}
|
<i class="bi bi-shield-lock text-success fs-4"></i>
|
||||||
</h5>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div class="card-body">
|
<h5 class="mb-1">{"Privacy & Security Settings"}</h5>
|
||||||
<div class="mb-4">
|
<p class="text-muted mb-0">{"Manage your security preferences and privacy controls"}</p>
|
||||||
<h6>{"Two-Factor Authentication"}</h6>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4 p-3 bg-light rounded-3">
|
||||||
|
<h6 class="fw-medium mb-3">{"Two-Factor Authentication"}</h6>
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" type="checkbox" id="twoFactorAuth" checked=true />
|
<input class="form-check-input" type="checkbox" id="twoFactorAuth" checked=true />
|
||||||
<label class="form-check-label" for="twoFactorAuth">
|
<label class="form-check-label fw-medium" for="twoFactorAuth">
|
||||||
{"Enable two-factor authentication"}
|
{"Enable two-factor authentication"}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<small class="text-muted">{"Adds an extra layer of security to your account"}</small>
|
<small class="text-muted">{"Adds an extra layer of security to your account"}</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4 p-3 bg-light rounded-3">
|
||||||
<h6>{"Login Notifications"}</h6>
|
<h6 class="fw-medium mb-3">{"Login Notifications"}</h6>
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" type="checkbox" id="loginNotifications" checked=true />
|
<input class="form-check-input" type="checkbox" id="loginNotifications" checked=true />
|
||||||
<label class="form-check-label" for="loginNotifications">
|
<label class="form-check-label fw-medium" for="loginNotifications">
|
||||||
{"Email me when someone logs into my account"}
|
{"Email me when someone logs into my account"}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-4 p-3 bg-light rounded-3">
|
||||||
<h6>{"Data Privacy"}</h6>
|
<h6 class="fw-medium mb-3">{"Data Privacy"}</h6>
|
||||||
<div class="form-check form-switch mb-2">
|
<div class="form-check form-switch mb-2">
|
||||||
<input class="form-check-input" type="checkbox" id="dataSharing" />
|
<input class="form-check-input" type="checkbox" id="dataSharing" />
|
||||||
<label class="form-check-label" for="dataSharing">
|
<label class="form-check-label fw-medium" for="dataSharing">
|
||||||
{"Allow anonymous usage analytics"}
|
{"Allow anonymous usage analytics"}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" type="checkbox" id="marketingEmails" />
|
<input class="form-check-input" type="checkbox" id="marketingEmails" />
|
||||||
<label class="form-check-label" for="marketingEmails">
|
<label class="form-check-label fw-medium" for="marketingEmails">
|
||||||
{"Receive marketing communications"}
|
{"Receive marketing communications"}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4 pt-3 border-top">
|
||||||
<button class="btn btn-primary me-2">{"Update Security Settings"}</button>
|
<button class="btn btn-primary me-2">{"Update Security Settings"}</button>
|
||||||
<button class="btn btn-outline-danger">{"Download My Data"}</button>
|
<button class="btn btn-outline-danger">{"Download My Data"}</button>
|
||||||
</div>
|
</div>
|
||||||
@ -385,14 +393,14 @@ pub fn person_administration_view(props: &PersonAdministrationViewProps) -> Html
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
// Subscription Tier Pane
|
// Subscription Tier Pane
|
||||||
<div class="col-lg-4 mb-4">
|
<div class="col-lg-4 mb-4">
|
||||||
<div class="card h-100">
|
<div class="card border-0 shadow-sm h-100">
|
||||||
<div class="card-header">
|
<div class="card-body p-4">
|
||||||
<h5 class="mb-0">
|
<div class="d-flex align-items-center mb-3">
|
||||||
<i class="bi bi-star me-2"></i>
|
<div class="bg-warning bg-opacity-10 rounded-3 p-2 me-3">
|
||||||
{"Current Plan"}
|
<i class="bi bi-star text-warning fs-5"></i>
|
||||||
</h5>
|
</div>
|
||||||
</div>
|
<h5 class="mb-0">{"Current Plan"}</h5>
|
||||||
<div class="card-body">
|
</div>
|
||||||
<div class="text-center mb-3">
|
<div class="text-center mb-3">
|
||||||
<div class="badge bg-primary fs-6 px-3 py-2 mb-2">{¤t_plan.name}</div>
|
<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>
|
<h3 class="text-primary mb-0">{format!("${:.0}", current_plan.price)}<small class="text-muted">{"/month"}</small></h3>
|
||||||
@ -438,14 +446,14 @@ pub fn person_administration_view(props: &PersonAdministrationViewProps) -> Html
|
|||||||
|
|
||||||
<div class="col-lg-8">
|
<div class="col-lg-8">
|
||||||
// Payments Table Pane
|
// Payments Table Pane
|
||||||
<div class="card mb-4">
|
<div class="card border-0 shadow-sm mb-4">
|
||||||
<div class="card-header">
|
<div class="card-body p-4">
|
||||||
<h5 class="mb-0">
|
<div class="d-flex align-items-center mb-3">
|
||||||
<i class="bi bi-receipt me-2"></i>
|
<div class="bg-info bg-opacity-10 rounded-3 p-2 me-3">
|
||||||
{"Payment History"}
|
<i class="bi bi-receipt text-info fs-5"></i>
|
||||||
</h5>
|
</div>
|
||||||
</div>
|
<h5 class="mb-0">{"Payment History"}</h5>
|
||||||
<div class="card-body">
|
</div>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
@ -483,26 +491,28 @@ pub fn person_administration_view(props: &PersonAdministrationViewProps) -> Html
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
// Payment Methods Pane
|
// Payment Methods Pane
|
||||||
<div class="card">
|
<div class="card border-0 shadow-sm">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-body p-4">
|
||||||
<h5 class="mb-0">
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
<i class="bi bi-credit-card me-2"></i>
|
<div class="d-flex align-items-center">
|
||||||
{"Payment Methods"}
|
<div class="bg-primary bg-opacity-10 rounded-3 p-2 me-3">
|
||||||
</h5>
|
<i class="bi bi-credit-card text-primary fs-5"></i>
|
||||||
<button
|
</div>
|
||||||
class="btn btn-primary btn-sm"
|
<h5 class="mb-0">{"Payment Methods"}</h5>
|
||||||
onclick={on_add_payment_method.clone()}
|
</div>
|
||||||
disabled={loading_action.as_ref().map_or(false, |action| action == "adding_payment")}
|
<button
|
||||||
>
|
class="btn btn-primary btn-sm"
|
||||||
<i class="bi bi-plus me-1"></i>
|
onclick={on_add_payment_method.clone()}
|
||||||
{if loading_action.as_ref().map_or(false, |action| action == "adding_payment") {
|
disabled={loading_action.as_ref().map_or(false, |action| action == "adding_payment")}
|
||||||
"Adding..."
|
>
|
||||||
} else {
|
<i class="bi bi-plus me-1"></i>
|
||||||
"Add Method"
|
{if loading_action.as_ref().map_or(false, |action| action == "adding_payment") {
|
||||||
}}
|
"Adding..."
|
||||||
</button>
|
} else {
|
||||||
</div>
|
"Add Method"
|
||||||
<div class="card-body">
|
}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{for billing_api.payment_methods.iter().map(|method| html! {
|
{for billing_api.payment_methods.iter().map(|method| html! {
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-6 mb-3">
|
||||||
@ -566,25 +576,28 @@ pub fn person_administration_view(props: &PersonAdministrationViewProps) -> Html
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 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 for your personal account.".to_string()}
|
|
||||||
primary_action={Some(("Browse Integrations".to_string(), "#".to_string()))}
|
|
||||||
secondary_action={Some(("API Documentation".to_string(), "#".to_string()))}
|
|
||||||
/>
|
|
||||||
});
|
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<>
|
<>
|
||||||
<ViewComponent
|
<div class="container-fluid px-3 px-md-4 px-lg-5 px-xl-6">
|
||||||
title={Some("Administration".to_string())}
|
<div class="row">
|
||||||
description={Some("Account settings, billing, integrations".to_string())}
|
<div class="col-12">
|
||||||
tabs={Some(tabs)}
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
default_tab={Some("Account Settings".to_string())}
|
<div>
|
||||||
/>
|
<h1 class="h3 mb-1">{"Settings"}</h1>
|
||||||
|
<p class="text-muted mb-0">{"Manage your account settings and preferences"}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ViewComponent
|
||||||
|
title={None::<String>}
|
||||||
|
description={None::<String>}
|
||||||
|
tabs={Some(tabs)}
|
||||||
|
default_tab={Some("Account Settings".to_string())}
|
||||||
|
use_modern_header={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
// Plan Selection Modal
|
// Plan Selection Modal
|
||||||
if *show_plan_modal {
|
if *show_plan_modal {
|
||||||
|
@ -74,7 +74,7 @@ pub fn residence_view(props: &ResidenceViewProps) -> Html {
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="fw-bold">{"Email:"}</td>
|
<td class="fw-bold">{"Email:"}</td>
|
||||||
<td>{"john.doe@resident.zdf"}</td>
|
<td>{"timur@resident.zdf"}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -1005,6 +1005,7 @@ pub fn treasury_view(_props: &TreasuryViewProps) -> Html {
|
|||||||
description={Some("Manage wallets, digital assets, and transactions".to_string())}
|
description={Some("Manage wallets, digital assets, and transactions".to_string())}
|
||||||
tabs={Some(tabs)}
|
tabs={Some(tabs)}
|
||||||
default_tab={Some("Overview".to_string())}
|
default_tab={Some("Overview".to_string())}
|
||||||
|
use_modern_header={true}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
// Import Wallet Modal
|
// Import Wallet Modal
|
||||||
|
Loading…
Reference in New Issue
Block a user