From 1c96fa4087be624eb9917c704011b89493185018 Mon Sep 17 00:00:00 2001 From: Timur Gordon <31495328+timurgordon@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:49:32 +0200 Subject: [PATCH] checkpoint --- platform/src/components/inbox.rs | 267 ++++++++++++++++++ platform/src/components/layout/sidebar.rs | 1 - platform/src/components/mod.rs | 6 +- platform/src/components/residence_card.rs | 145 ++++++++++ platform/src/components/view_component.rs | 188 ++++++++---- platform/src/views/accounting_view.rs | 36 ++- platform/src/views/companies_view.rs | 40 +-- platform/src/views/contracts_view.rs | 74 +++-- platform/src/views/home_view.rs | 173 ++++++++---- .../src/views/person_administration_view.rs | 185 ++++++------ platform/src/views/residence_view.rs | 2 +- platform/src/views/treasury_view.rs | 1 + 12 files changed, 856 insertions(+), 262 deletions(-) create mode 100644 platform/src/components/inbox.rs create mode 100644 platform/src/components/residence_card.rs diff --git a/platform/src/components/inbox.rs b/platform/src/components/inbox.rs new file mode 100644 index 0000000..52fb0fa --- /dev/null +++ b/platform/src/components/inbox.rs @@ -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, + pub action_url: Option, +} + +#[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, +} + +#[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! { + <> + + +
+
+
+
+ +
{"Inbox"}
+
+ if unread_count > 0 { + {unread_count} + } +
+ +
+ {for notifications.iter().take(4).map(|notification| { + html! { +
+
+
+ +
+ +
+
+
+ {¬ification.title} +
+ + {¬ification.timestamp} + +
+ +

+ {¬ification.message} +

+ + if let Some(action_text) = ¬ification.action_text { + + } +
+
+
+ } + })} +
+ + if notifications.len() > 4 { +
+ +
+ } +
+
+ + } +} \ No newline at end of file diff --git a/platform/src/components/layout/sidebar.rs b/platform/src/components/layout/sidebar.rs index 8aa929c..192d8ef 100644 --- a/platform/src/components/layout/sidebar.rs +++ b/platform/src/components/layout/sidebar.rs @@ -26,7 +26,6 @@ pub fn sidebar(props: &SidebarProps) -> Html { AppView::Home, AppView::Administration, AppView::PersonAdministration, - AppView::Residence, AppView::Accounting, AppView::Contracts, AppView::Governance, diff --git a/platform/src/components/mod.rs b/platform/src/components/mod.rs index 031b729..ecb6b71 100644 --- a/platform/src/components/mod.rs +++ b/platform/src/components/mod.rs @@ -8,6 +8,8 @@ pub mod toast; pub mod common; pub mod accounting; pub mod resident_landing_overlay; +pub mod inbox; +pub mod residence_card; pub use layout::*; pub use forms::*; @@ -18,4 +20,6 @@ pub use entities::*; pub use toast::*; pub use common::*; pub use accounting::*; -pub use resident_landing_overlay::*; \ No newline at end of file +pub use resident_landing_overlay::*; +pub use inbox::*; +pub use residence_card::*; \ No newline at end of file diff --git a/platform/src/components/residence_card.rs b/platform/src/components/residence_card.rs new file mode 100644 index 0000000..15f9301 --- /dev/null +++ b/platform/src/components/residence_card.rs @@ -0,0 +1,145 @@ +use yew::prelude::*; + +#[derive(Properties, PartialEq)] +pub struct ResidenceCardProps { + pub user_name: String, + #[prop_or_default] + pub email: Option, + #[prop_or_default] + pub public_key: Option, + #[prop_or_default] + pub resident_id: Option, + #[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! { + <> + + +
+
+
+ // Header with Zanzibar flag gradient +
+
+
+
{"DIGITAL RESIDENT"}
+ {"Zanzibar Digital Freezone"} +
+ +
+
+ + // Card body with white background +
+
+
{"FULL NAME"}
+
+ {&props.user_name} +
+
+ +
+
{"EMAIL"}
+
+ {props.email.as_ref().unwrap_or(&"resident@zanzibar-freezone.com".to_string())} +
+
+ +
+
+ + {"PUBLIC KEY"} +
+
+ {if let Some(public_key) = &props.public_key { + format!("{}...", &public_key[..std::cmp::min(24, public_key.len())]) + } else { + "zdf1qxy2mlyjkjkpskpsw9fxtpugs450add72nyktmzqau...".to_string() + }} +
+
+ +
+
{"RESIDENT SINCE"}
+
+ {"2025"} +
+
+ +
+
+
{"RESIDENT ID"}
+
+ {props.resident_id.as_ref().unwrap_or(&"ZDF-2025-****".to_string())} +
+
+
+
{"STATUS"}
+
+ {props.status.get_text()} +
+
+
+ + // QR Code at bottom +
+
+
+
+
{"Scan to verify"}
+
+
+
+
+
+ + } +} \ No newline at end of file diff --git a/platform/src/components/view_component.rs b/platform/src/components/view_component.rs index f987a47..4a9493e 100644 --- a/platform/src/components/view_component.rs +++ b/platform/src/components/view_component.rs @@ -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) #[prop_or_default] 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)] @@ -40,7 +42,8 @@ pub fn view_component(props: &ViewComponentProps) -> Html { }; html! { -
+
+
// Breadcrumbs (if provided) if let Some(breadcrumbs) = &props.breadcrumbs { } - // Page Header in Card (with integrated tabs if provided) - if props.title.is_some() || props.description.is_some() || props.actions.is_some() || props.tabs.is_some() { -
-
-
-
-
- // Left side: Title and description -
- if let Some(title) = &props.title { -

{title}

+ if props.use_modern_header { + // Modern header style without card wrapper + if props.title.is_some() || props.description.is_some() || props.actions.is_some() { +
+ // Left side: Title and description +
+ if let Some(title) = &props.title { +

{title}

+ } + if let Some(description) = &props.description { +

{description}

+ } +
+ + // Right side: Actions + if let Some(actions) = &props.actions { +
+ {actions.clone()} +
+ } +
+ } + + // Modern tabs navigation (if provided) + if let Some(tabs) = &props.tabs { +
+ +
+ } + } else { + // Original header style with card wrapper + if props.title.is_some() || props.description.is_some() || props.actions.is_some() || props.tabs.is_some() { +
+
+
+
+
+ // Left side: Title and description +
+ if let Some(title) = &props.title { +

{title}

+ } + if let Some(description) = &props.description { +

{description}

+ } +
+ + // Center: Tabs navigation (if provided) + if let Some(tabs) = &props.tabs { +
+ +
} - if let Some(description) = &props.description { -

{description}

+ + // Right side: Actions + if let Some(actions) = &props.actions { +
+ {actions.clone()} +
}
- - // Center: Tabs navigation (if provided) - if let Some(tabs) = &props.tabs { -
- -
- } - - // Right side: Actions - if let Some(actions) = &props.actions { -
- {actions.clone()} -
- }
-
+ } } // Tab Content (if tabs are provided) if let Some(tabs) = &props.tabs { -
+
{for tabs.iter().map(|(tab_name, content)| { let is_active = *active_tab == *tab_name; html! { @@ -147,6 +222,7 @@ pub fn view_component(props: &ViewComponentProps) -> Html { // No tabs, render children directly {for props.children.iter()} } +
} } \ No newline at end of file diff --git a/platform/src/views/accounting_view.rs b/platform/src/views/accounting_view.rs index ec45f31..88dcb04 100644 --- a/platform/src/views/accounting_view.rs +++ b/platform/src/views/accounting_view.rs @@ -254,12 +254,30 @@ pub fn accounting_view(props: &AccountingViewProps) -> Html { }); }, ViewContext::Person => { - // For personal context, show simplified version - tabs.insert("Income Tracking".to_string(), html! { -
- - {"Personal accounting features coming soon. Switch to Business context for full accounting functionality."} -
+ // Show same functionality as business context + // Overview Tab + tabs.insert("Overview".to_string(), html! { + + }); + + // Revenue Tab + tabs.insert("Revenue".to_string(), html! { + + }); + + // Expenses Tab + tabs.insert("Expenses".to_string(), html! { + + }); + + // Tax Tab + tabs.insert("Tax".to_string(), html! { + + }); + + // Financial Reports Tab + tabs.insert("Financial Reports".to_string(), html! { + }); } } @@ -274,10 +292,8 @@ pub fn accounting_view(props: &AccountingViewProps) -> Html { title={Some(title.to_string())} description={Some(description.to_string())} tabs={Some(tabs)} - default_tab={match context { - ViewContext::Business => Some("Overview".to_string()), - ViewContext::Person => Some("Income Tracking".to_string()), - }} + default_tab={Some("Overview".to_string())} + use_modern_header={true} /> } } \ No newline at end of file diff --git a/platform/src/views/companies_view.rs b/platform/src/views/companies_view.rs index e0f8802..06e89e7 100644 --- a/platform/src/views/companies_view.rs +++ b/platform/src/views/companies_view.rs @@ -164,6 +164,7 @@ impl Component for CompaniesView { {self.render_companies_content(ctx)} @@ -258,24 +261,27 @@ impl CompaniesView { let link = ctx.link(); html! { -
-
-
-
- {"Companies & Registrations"} -
- - {format!("{} companies, {} pending registrations", self.companies.len(), self.registrations.len())} - +
+
+
+
+
+ +
+
+
{"Companies & Registrations"}
+ + {format!("{} companies, {} pending registrations", self.companies.len(), self.registrations.len())} + +
+
+
- -
-
diff --git a/platform/src/views/contracts_view.rs b/platform/src/views/contracts_view.rs index 97fb65b..54c302c 100644 --- a/platform/src/views/contracts_view.rs +++ b/platform/src/views/contracts_view.rs @@ -170,10 +170,11 @@ impl Component for ContractsViewComponent { html! { } } @@ -296,11 +297,14 @@ impl ContractsViewComponent { // Filters Section
-
-
-
{"Filters"}
-
-
+
+
+
+
+ +
+
{"Filters"}
+
@@ -344,11 +348,14 @@ impl ContractsViewComponent { // Contracts Table
-
-
-
{"Contracts"}
-
-
+
+
+
+
+ +
+
{"Contracts"}
+
{self.render_contracts_table(_ctx)}
@@ -442,11 +449,14 @@ impl ContractsViewComponent { html! {
-
-
-
{"Contract Details"}
-
-
+
+
+
+
+ +
+
{"Contract Details"}
+
-
-
-
{"Tips"}
-
-
+
+
+
+
+ +
+
{"Tips"}
+

{"Creating a new contract is just the first step. After creating the contract, you'll be able to:"}

  • {"Add signers who need to approve the contract"}
  • @@ -547,11 +560,14 @@ Payment will be made according to the following schedule:
-
-
-
{"Contract Templates"}
-
-
+
+
+
+
+ +
+
{"Contract Templates"}
+

{"You can use one of our pre-defined templates to get started quickly:"}

@@ -323,52 +327,56 @@ pub fn person_administration_view(props: &PersonAdministrationViewProps) -> Html // Privacy & Security Tab tabs.insert("Privacy & Security".to_string(), html! { -
-
-
- - {"Privacy & Security Settings"} -
-
-
-
-
{"Two-Factor Authentication"}
+
+
+
+
+ +
+
+
{"Privacy & Security Settings"}
+

{"Manage your security preferences and privacy controls"}

+
+
+ +
+
{"Two-Factor Authentication"}
-
{"Adds an extra layer of security to your account"}
-
-
{"Login Notifications"}
+
+
{"Login Notifications"}
-
-
-
{"Data Privacy"}
+
+
{"Data Privacy"}
-
-
-
+
@@ -385,14 +393,14 @@ pub fn person_administration_view(props: &PersonAdministrationViewProps) -> Html
// Subscription Tier Pane
-
-
-
- - {"Current Plan"} -
-
-
+
+
+
+
+ +
+
{"Current Plan"}
+
{¤t_plan.name}

{format!("${:.0}", current_plan.price)}{"/month"}

@@ -438,14 +446,14 @@ pub fn person_administration_view(props: &PersonAdministrationViewProps) -> Html
// Payments Table Pane -
-
-
- - {"Payment History"} -
-
-
+
+
+
+
+ +
+
{"Payment History"}
+
@@ -483,26 +491,28 @@ pub fn person_administration_view(props: &PersonAdministrationViewProps) -> Html // Payment Methods Pane -
-
-
- - {"Payment Methods"} -
- -
-
+
+
+
+
+
+ +
+
{"Payment Methods"}
+
+ +
{for billing_api.payment_methods.iter().map(|method| html! {
@@ -566,25 +576,28 @@ pub fn person_administration_view(props: &PersonAdministrationViewProps) -> Html } }); - // Integrations Tab - tabs.insert("Integrations".to_string(), html! { - - }); - html! { <> - +
+
+
+
+
+

{"Settings"}

+

{"Manage your account settings and preferences"}

+
+
+ + } + description={None::} + tabs={Some(tabs)} + default_tab={Some("Account Settings".to_string())} + use_modern_header={true} + /> +
+
+
// Plan Selection Modal if *show_plan_modal { diff --git a/platform/src/views/residence_view.rs b/platform/src/views/residence_view.rs index 4981af8..0a01054 100644 --- a/platform/src/views/residence_view.rs +++ b/platform/src/views/residence_view.rs @@ -74,7 +74,7 @@ pub fn residence_view(props: &ResidenceViewProps) -> Html {
- +
{"Email:"}{"john.doe@resident.zdf"}{"timur@resident.zdf"}
diff --git a/platform/src/views/treasury_view.rs b/platform/src/views/treasury_view.rs index 23ba70b..2837913 100644 --- a/platform/src/views/treasury_view.rs +++ b/platform/src/views/treasury_view.rs @@ -1005,6 +1005,7 @@ pub fn treasury_view(_props: &TreasuryViewProps) -> Html { description={Some("Manage wallets, digital assets, and transactions".to_string())} tabs={Some(tabs)} default_tab={Some("Overview".to_string())} + use_modern_header={true} /> // Import Wallet Modal