initial commit
This commit is contained in:
		
							
								
								
									
										392
									
								
								platform/src/services/company_service.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										392
									
								
								platform/src/services/company_service.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,392 @@
 | 
			
		||||
use crate::models::*;
 | 
			
		||||
use gloo::storage::{LocalStorage, Storage};
 | 
			
		||||
use serde_json;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
 | 
			
		||||
const COMPANIES_STORAGE_KEY: &str = "freezone_companies";
 | 
			
		||||
const REGISTRATION_FORM_KEY: &str = "freezone_registration_form";
 | 
			
		||||
const REGISTRATIONS_STORAGE_KEY: &str = "freezone_registrations";
 | 
			
		||||
const FORM_EXPIRY_HOURS: i64 = 24;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
 | 
			
		||||
pub struct CompanyRegistration {
 | 
			
		||||
    pub id: u32,
 | 
			
		||||
    pub company_name: String,
 | 
			
		||||
    pub company_type: CompanyType,
 | 
			
		||||
    pub status: RegistrationStatus,
 | 
			
		||||
    pub created_at: String,
 | 
			
		||||
    pub form_data: CompanyFormData,
 | 
			
		||||
    pub current_step: u8,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
 | 
			
		||||
pub enum RegistrationStatus {
 | 
			
		||||
    Draft,
 | 
			
		||||
    PendingPayment,
 | 
			
		||||
    PaymentFailed,
 | 
			
		||||
    PendingApproval,
 | 
			
		||||
    Approved,
 | 
			
		||||
    Rejected,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RegistrationStatus {
 | 
			
		||||
    pub fn to_string(&self) -> &'static str {
 | 
			
		||||
        match self {
 | 
			
		||||
            RegistrationStatus::Draft => "Draft",
 | 
			
		||||
            RegistrationStatus::PendingPayment => "Pending Payment",
 | 
			
		||||
            RegistrationStatus::PaymentFailed => "Payment Failed",
 | 
			
		||||
            RegistrationStatus::PendingApproval => "Pending Approval",
 | 
			
		||||
            RegistrationStatus::Approved => "Approved",
 | 
			
		||||
            RegistrationStatus::Rejected => "Rejected",
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    pub fn get_badge_class(&self) -> &'static str {
 | 
			
		||||
        match self {
 | 
			
		||||
            RegistrationStatus::Draft => "bg-secondary",
 | 
			
		||||
            RegistrationStatus::PendingPayment => "bg-warning",
 | 
			
		||||
            RegistrationStatus::PaymentFailed => "bg-danger",
 | 
			
		||||
            RegistrationStatus::PendingApproval => "bg-info",
 | 
			
		||||
            RegistrationStatus::Approved => "bg-success",
 | 
			
		||||
            RegistrationStatus::Rejected => "bg-danger",
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct CompanyService;
 | 
			
		||||
 | 
			
		||||
impl CompanyService {
 | 
			
		||||
    /// Get all companies from local storage
 | 
			
		||||
    pub fn get_companies() -> Vec<Company> {
 | 
			
		||||
        match LocalStorage::get::<Vec<Company>>(COMPANIES_STORAGE_KEY) {
 | 
			
		||||
            Ok(companies) => companies,
 | 
			
		||||
            Err(_) => {
 | 
			
		||||
                // Initialize with empty list if not found
 | 
			
		||||
                let companies = Vec::new();
 | 
			
		||||
                let _ = LocalStorage::set(COMPANIES_STORAGE_KEY, &companies);
 | 
			
		||||
                companies
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Save companies to local storage
 | 
			
		||||
    pub fn save_companies(companies: &[Company]) -> Result<(), String> {
 | 
			
		||||
        LocalStorage::set(COMPANIES_STORAGE_KEY, companies)
 | 
			
		||||
            .map_err(|e| format!("Failed to save companies: {:?}", e))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Add a new company
 | 
			
		||||
    pub fn add_company(mut company: Company) -> Result<Company, String> {
 | 
			
		||||
        let mut companies = Self::get_companies();
 | 
			
		||||
        
 | 
			
		||||
        // Generate new ID
 | 
			
		||||
        let max_id = companies.iter().map(|c| c.id).max().unwrap_or(0);
 | 
			
		||||
        company.id = max_id + 1;
 | 
			
		||||
        
 | 
			
		||||
        // Generate registration number
 | 
			
		||||
        company.registration_number = Self::generate_registration_number(&company.name);
 | 
			
		||||
        
 | 
			
		||||
        companies.push(company.clone());
 | 
			
		||||
        Self::save_companies(&companies)?;
 | 
			
		||||
        
 | 
			
		||||
        Ok(company)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Update an existing company
 | 
			
		||||
    pub fn update_company(updated_company: &Company) -> Result<(), String> {
 | 
			
		||||
        let mut companies = Self::get_companies();
 | 
			
		||||
        
 | 
			
		||||
        if let Some(company) = companies.iter_mut().find(|c| c.id == updated_company.id) {
 | 
			
		||||
            *company = updated_company.clone();
 | 
			
		||||
            Self::save_companies(&companies)?;
 | 
			
		||||
            Ok(())
 | 
			
		||||
        } else {
 | 
			
		||||
            Err("Company not found".to_string())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Delete a company
 | 
			
		||||
    pub fn delete_company(company_id: u32) -> Result<(), String> {
 | 
			
		||||
        let mut companies = Self::get_companies();
 | 
			
		||||
        companies.retain(|c| c.id != company_id);
 | 
			
		||||
        Self::save_companies(&companies)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get company by ID
 | 
			
		||||
    pub fn get_company_by_id(company_id: u32) -> Option<Company> {
 | 
			
		||||
        Self::get_companies().into_iter().find(|c| c.id == company_id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Generate a registration number
 | 
			
		||||
    fn generate_registration_number(company_name: &str) -> String {
 | 
			
		||||
        let date = js_sys::Date::new_0();
 | 
			
		||||
        let year = date.get_full_year();
 | 
			
		||||
        let month = date.get_month() + 1; // JS months are 0-based
 | 
			
		||||
        let day = date.get_date();
 | 
			
		||||
        
 | 
			
		||||
        let prefix = company_name
 | 
			
		||||
            .chars()
 | 
			
		||||
            .take(3)
 | 
			
		||||
            .collect::<String>()
 | 
			
		||||
            .to_uppercase();
 | 
			
		||||
            
 | 
			
		||||
        format!("FZC-{:04}{:02}{:02}-{}", year, month, day, prefix)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Save registration form data with expiration
 | 
			
		||||
    pub fn save_registration_form(form_data: &CompanyFormData, current_step: u8) -> Result<(), String> {
 | 
			
		||||
        let now = js_sys::Date::now() as i64;
 | 
			
		||||
        let expires_at = now + (FORM_EXPIRY_HOURS * 60 * 60 * 1000);
 | 
			
		||||
        
 | 
			
		||||
        let saved_form = SavedRegistrationForm {
 | 
			
		||||
            form_data: form_data.clone(),
 | 
			
		||||
            current_step,
 | 
			
		||||
            saved_at: now,
 | 
			
		||||
            expires_at,
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        LocalStorage::set(REGISTRATION_FORM_KEY, &saved_form)
 | 
			
		||||
            .map_err(|e| format!("Failed to save form: {:?}", e))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Load registration form data if not expired
 | 
			
		||||
    pub fn load_registration_form() -> Option<(CompanyFormData, u8)> {
 | 
			
		||||
        match LocalStorage::get::<SavedRegistrationForm>(REGISTRATION_FORM_KEY) {
 | 
			
		||||
            Ok(saved_form) => {
 | 
			
		||||
                let now = js_sys::Date::now() as i64;
 | 
			
		||||
                if now < saved_form.expires_at {
 | 
			
		||||
                    Some((saved_form.form_data, saved_form.current_step))
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Form expired, remove it
 | 
			
		||||
                    let _ = LocalStorage::delete(REGISTRATION_FORM_KEY);
 | 
			
		||||
                    None
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Err(_) => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Clear saved registration form
 | 
			
		||||
    pub fn clear_registration_form() -> Result<(), String> {
 | 
			
		||||
        LocalStorage::delete(REGISTRATION_FORM_KEY);
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Validate form data for a specific step
 | 
			
		||||
    pub fn validate_step(form_data: &CompanyFormData, step: u8) -> ValidationResult {
 | 
			
		||||
        let mut errors = Vec::new();
 | 
			
		||||
        
 | 
			
		||||
        match step {
 | 
			
		||||
            1 => {
 | 
			
		||||
                if form_data.company_name.trim().is_empty() {
 | 
			
		||||
                    errors.push("Company name is required".to_string());
 | 
			
		||||
                } else if form_data.company_name.len() < 2 {
 | 
			
		||||
                    errors.push("Company name must be at least 2 characters".to_string());
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                if form_data.company_email.trim().is_empty() {
 | 
			
		||||
                    errors.push("Company email is required".to_string());
 | 
			
		||||
                } else if !Self::is_valid_email(&form_data.company_email) {
 | 
			
		||||
                    errors.push("Please enter a valid email address".to_string());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            2 => {
 | 
			
		||||
                // Company type is always valid since it's a dropdown
 | 
			
		||||
            }
 | 
			
		||||
            3 => {
 | 
			
		||||
                if form_data.shareholders.is_empty() {
 | 
			
		||||
                    errors.push("At least one shareholder is required".to_string());
 | 
			
		||||
                } else {
 | 
			
		||||
                    let total_percentage: f64 = form_data.shareholders.iter().map(|s| s.percentage).sum();
 | 
			
		||||
                    if (total_percentage - 100.0).abs() > 0.01 {
 | 
			
		||||
                        errors.push(format!("Shareholder percentages must add up to 100% (currently {:.1}%)", total_percentage));
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    for (i, shareholder) in form_data.shareholders.iter().enumerate() {
 | 
			
		||||
                        if shareholder.name.trim().is_empty() {
 | 
			
		||||
                            errors.push(format!("Shareholder {} name is required", i + 1));
 | 
			
		||||
                        }
 | 
			
		||||
                        if shareholder.percentage <= 0.0 || shareholder.percentage > 100.0 {
 | 
			
		||||
                            errors.push(format!("Shareholder {} percentage must be between 0 and 100", i + 1));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            4 => {
 | 
			
		||||
                if !form_data.legal_agreements.all_agreed() {
 | 
			
		||||
                    let missing = form_data.legal_agreements.missing_agreements();
 | 
			
		||||
                    errors.push(format!("Please accept all required agreements: {}", missing.join(", ")));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            _ => {
 | 
			
		||||
                errors.push("Invalid step".to_string());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if errors.is_empty() {
 | 
			
		||||
            ValidationResult::valid()
 | 
			
		||||
        } else {
 | 
			
		||||
            ValidationResult::invalid(errors)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Simple email validation
 | 
			
		||||
    fn is_valid_email(email: &str) -> bool {
 | 
			
		||||
        email.contains('@') && email.contains('.') && email.len() > 5
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Create a company from form data (simulated)
 | 
			
		||||
    pub fn create_company_from_form(form_data: &CompanyFormData) -> Result<Company, String> {
 | 
			
		||||
        let now = js_sys::Date::new_0();
 | 
			
		||||
        let incorporation_date = format!(
 | 
			
		||||
            "{:04}-{:02}-{:02}",
 | 
			
		||||
            now.get_full_year(),
 | 
			
		||||
            now.get_month() + 1,
 | 
			
		||||
            now.get_date()
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        let company = Company {
 | 
			
		||||
            id: 0, // Will be set by add_company
 | 
			
		||||
            name: form_data.company_name.clone(),
 | 
			
		||||
            company_type: form_data.company_type.clone(),
 | 
			
		||||
            status: CompanyStatus::PendingPayment,
 | 
			
		||||
            registration_number: String::new(), // Will be generated by add_company
 | 
			
		||||
            incorporation_date,
 | 
			
		||||
            email: Some(form_data.company_email.clone()),
 | 
			
		||||
            phone: Some(form_data.company_phone.clone()),
 | 
			
		||||
            website: form_data.company_website.clone(),
 | 
			
		||||
            address: Some(form_data.company_address.clone()),
 | 
			
		||||
            industry: form_data.company_industry.clone(),
 | 
			
		||||
            description: form_data.company_purpose.clone(),
 | 
			
		||||
            fiscal_year_end: form_data.fiscal_year_end.clone(),
 | 
			
		||||
            shareholders: form_data.shareholders.clone(),
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        Self::add_company(company)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Calculate total payment amount
 | 
			
		||||
    pub fn calculate_payment_amount(company_type: &CompanyType, payment_plan: &PaymentPlan) -> f64 {
 | 
			
		||||
        let pricing = company_type.get_pricing();
 | 
			
		||||
        let twin_fee = 2.0; // ZDFZ Twin fee
 | 
			
		||||
        let monthly_total = pricing.monthly_fee + twin_fee;
 | 
			
		||||
        
 | 
			
		||||
        let subscription_amount = match payment_plan {
 | 
			
		||||
            PaymentPlan::Monthly => monthly_total,
 | 
			
		||||
            PaymentPlan::Yearly => monthly_total * 12.0 * payment_plan.get_discount(),
 | 
			
		||||
            PaymentPlan::TwoYear => monthly_total * 24.0 * payment_plan.get_discount(),
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        pricing.setup_fee + subscription_amount
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Initialize with sample data for demonstration
 | 
			
		||||
    pub fn initialize_sample_data() -> Result<(), String> {
 | 
			
		||||
        let companies = Self::get_companies();
 | 
			
		||||
        if companies.is_empty() {
 | 
			
		||||
            let sample_companies = vec![
 | 
			
		||||
                Company {
 | 
			
		||||
                    id: 1,
 | 
			
		||||
                    name: "Zanzibar Digital Solutions".to_string(),
 | 
			
		||||
                    company_type: CompanyType::StartupFZC,
 | 
			
		||||
                    status: CompanyStatus::Active,
 | 
			
		||||
                    registration_number: "FZC-20250101-ZAN".to_string(),
 | 
			
		||||
                    incorporation_date: "2025-01-01".to_string(),
 | 
			
		||||
                    email: Some("contact@zanzibar-digital.com".to_string()),
 | 
			
		||||
                    phone: Some("+255 123 456 789".to_string()),
 | 
			
		||||
                    website: Some("https://zanzibar-digital.com".to_string()),
 | 
			
		||||
                    address: Some("Stone Town, Zanzibar".to_string()),
 | 
			
		||||
                    industry: Some("Technology".to_string()),
 | 
			
		||||
                    description: Some("Digital solutions and blockchain development".to_string()),
 | 
			
		||||
                    fiscal_year_end: Some("12-31".to_string()),
 | 
			
		||||
                    shareholders: vec![
 | 
			
		||||
                        Shareholder {
 | 
			
		||||
                            name: "John Smith".to_string(),
 | 
			
		||||
                            resident_id: "ID123456789".to_string(),
 | 
			
		||||
                            percentage: 60.0,
 | 
			
		||||
                        },
 | 
			
		||||
                        Shareholder {
 | 
			
		||||
                            name: "Sarah Johnson".to_string(),
 | 
			
		||||
                            resident_id: "ID987654321".to_string(),
 | 
			
		||||
                            percentage: 40.0,
 | 
			
		||||
                        },
 | 
			
		||||
                    ],
 | 
			
		||||
                },
 | 
			
		||||
                Company {
 | 
			
		||||
                    id: 2,
 | 
			
		||||
                    name: "Ocean Trading Co".to_string(),
 | 
			
		||||
                    company_type: CompanyType::GrowthFZC,
 | 
			
		||||
                    status: CompanyStatus::Active,
 | 
			
		||||
                    registration_number: "FZC-20250102-OCE".to_string(),
 | 
			
		||||
                    incorporation_date: "2025-01-02".to_string(),
 | 
			
		||||
                    email: Some("info@ocean-trading.com".to_string()),
 | 
			
		||||
                    phone: Some("+255 987 654 321".to_string()),
 | 
			
		||||
                    website: None,
 | 
			
		||||
                    address: Some("Pemba Island, Zanzibar".to_string()),
 | 
			
		||||
                    industry: Some("Trading".to_string()),
 | 
			
		||||
                    description: Some("International trading and logistics".to_string()),
 | 
			
		||||
                    fiscal_year_end: Some("06-30".to_string()),
 | 
			
		||||
                    shareholders: vec![
 | 
			
		||||
                        Shareholder {
 | 
			
		||||
                            name: "Ahmed Hassan".to_string(),
 | 
			
		||||
                            resident_id: "ID555666777".to_string(),
 | 
			
		||||
                            percentage: 100.0,
 | 
			
		||||
                        },
 | 
			
		||||
                    ],
 | 
			
		||||
                },
 | 
			
		||||
            ];
 | 
			
		||||
            
 | 
			
		||||
            Self::save_companies(&sample_companies)?;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get all registrations from local storage
 | 
			
		||||
    pub fn get_registrations() -> Vec<CompanyRegistration> {
 | 
			
		||||
        match LocalStorage::get::<Vec<CompanyRegistration>>(REGISTRATIONS_STORAGE_KEY) {
 | 
			
		||||
            Ok(registrations) => registrations,
 | 
			
		||||
            Err(_) => {
 | 
			
		||||
                // Initialize with empty list if not found
 | 
			
		||||
                let registrations = Vec::new();
 | 
			
		||||
                let _ = LocalStorage::set(REGISTRATIONS_STORAGE_KEY, ®istrations);
 | 
			
		||||
                registrations
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Save registrations to local storage
 | 
			
		||||
    pub fn save_registrations(registrations: &[CompanyRegistration]) -> Result<(), String> {
 | 
			
		||||
        LocalStorage::set(REGISTRATIONS_STORAGE_KEY, registrations)
 | 
			
		||||
            .map_err(|e| format!("Failed to save registrations: {:?}", e))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Add or update a registration
 | 
			
		||||
    pub fn save_registration(mut registration: CompanyRegistration) -> Result<CompanyRegistration, String> {
 | 
			
		||||
        let mut registrations = Self::get_registrations();
 | 
			
		||||
        
 | 
			
		||||
        if registration.id == 0 {
 | 
			
		||||
            // Generate new ID for new registration
 | 
			
		||||
            let max_id = registrations.iter().map(|r| r.id).max().unwrap_or(0);
 | 
			
		||||
            registration.id = max_id + 1;
 | 
			
		||||
            registrations.push(registration.clone());
 | 
			
		||||
        } else {
 | 
			
		||||
            // Update existing registration
 | 
			
		||||
            if let Some(existing) = registrations.iter_mut().find(|r| r.id == registration.id) {
 | 
			
		||||
                *existing = registration.clone();
 | 
			
		||||
            } else {
 | 
			
		||||
                return Err("Registration not found".to_string());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        Self::save_registrations(®istrations)?;
 | 
			
		||||
        Ok(registration)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
 | 
			
		||||
struct SavedRegistrationForm {
 | 
			
		||||
    form_data: CompanyFormData,
 | 
			
		||||
    current_step: u8,
 | 
			
		||||
    saved_at: i64,
 | 
			
		||||
    expires_at: i64,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										223
									
								
								platform/src/services/mock_billing_api.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								platform/src/services/mock_billing_api.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,223 @@
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
 | 
			
		||||
pub struct Plan {
 | 
			
		||||
    pub id: String,
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub price: f64,
 | 
			
		||||
    pub features: Vec<String>,
 | 
			
		||||
    pub popular: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct PaymentMethod {
 | 
			
		||||
    pub id: String,
 | 
			
		||||
    pub method_type: String,
 | 
			
		||||
    pub last_four: String,
 | 
			
		||||
    pub expires: Option<String>,
 | 
			
		||||
    pub is_primary: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct Invoice {
 | 
			
		||||
    pub id: String,
 | 
			
		||||
    pub date: String,
 | 
			
		||||
    pub description: String,
 | 
			
		||||
    pub amount: f64,
 | 
			
		||||
    pub status: String,
 | 
			
		||||
    pub pdf_url: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Serialize, Deserialize)]
 | 
			
		||||
pub struct Subscription {
 | 
			
		||||
    pub plan: Plan,
 | 
			
		||||
    pub next_billing_date: String,
 | 
			
		||||
    pub status: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct MockBillingApi {
 | 
			
		||||
    pub current_subscription: Subscription,
 | 
			
		||||
    pub available_plans: Vec<Plan>,
 | 
			
		||||
    pub payment_methods: Vec<PaymentMethod>,
 | 
			
		||||
    pub invoices: Vec<Invoice>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for MockBillingApi {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self::new()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MockBillingApi {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        let available_plans = vec![
 | 
			
		||||
            Plan {
 | 
			
		||||
                id: "starter".to_string(),
 | 
			
		||||
                name: "Starter".to_string(),
 | 
			
		||||
                price: 29.0,
 | 
			
		||||
                features: vec![
 | 
			
		||||
                    "Up to 100 transactions".to_string(),
 | 
			
		||||
                    "Basic reporting".to_string(),
 | 
			
		||||
                    "Email support".to_string(),
 | 
			
		||||
                ],
 | 
			
		||||
                popular: false,
 | 
			
		||||
            },
 | 
			
		||||
            Plan {
 | 
			
		||||
                id: "business_pro".to_string(),
 | 
			
		||||
                name: "Business Pro".to_string(),
 | 
			
		||||
                price: 99.0,
 | 
			
		||||
                features: vec![
 | 
			
		||||
                    "Unlimited transactions".to_string(),
 | 
			
		||||
                    "Advanced reporting".to_string(),
 | 
			
		||||
                    "Priority support".to_string(),
 | 
			
		||||
                    "API access".to_string(),
 | 
			
		||||
                ],
 | 
			
		||||
                popular: true,
 | 
			
		||||
            },
 | 
			
		||||
            Plan {
 | 
			
		||||
                id: "enterprise".to_string(),
 | 
			
		||||
                name: "Enterprise".to_string(),
 | 
			
		||||
                price: 299.0,
 | 
			
		||||
                features: vec![
 | 
			
		||||
                    "Unlimited everything".to_string(),
 | 
			
		||||
                    "Custom integrations".to_string(),
 | 
			
		||||
                    "Dedicated support".to_string(),
 | 
			
		||||
                    "SLA guarantee".to_string(),
 | 
			
		||||
                    "White-label options".to_string(),
 | 
			
		||||
                ],
 | 
			
		||||
                popular: false,
 | 
			
		||||
            },
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let current_subscription = Subscription {
 | 
			
		||||
            plan: available_plans[1].clone(), // Business Pro
 | 
			
		||||
            next_billing_date: "January 15, 2025".to_string(),
 | 
			
		||||
            status: "active".to_string(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let payment_methods = vec![
 | 
			
		||||
            PaymentMethod {
 | 
			
		||||
                id: "card_4242".to_string(),
 | 
			
		||||
                method_type: "Credit Card".to_string(),
 | 
			
		||||
                last_four: "4242".to_string(),
 | 
			
		||||
                expires: Some("12/26".to_string()),
 | 
			
		||||
                is_primary: true,
 | 
			
		||||
            },
 | 
			
		||||
            PaymentMethod {
 | 
			
		||||
                id: "bank_5678".to_string(),
 | 
			
		||||
                method_type: "Bank Transfer".to_string(),
 | 
			
		||||
                last_four: "5678".to_string(),
 | 
			
		||||
                expires: None,
 | 
			
		||||
                is_primary: false,
 | 
			
		||||
            },
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        let invoices = vec![
 | 
			
		||||
            Invoice {
 | 
			
		||||
                id: "inv_001".to_string(),
 | 
			
		||||
                date: "Dec 15, 2024".to_string(),
 | 
			
		||||
                description: "Business Pro - Monthly".to_string(),
 | 
			
		||||
                amount: 99.0,
 | 
			
		||||
                status: "Paid".to_string(),
 | 
			
		||||
                pdf_url: "data:application/pdf;base64,JVBERi0xLjQKJdPr6eEKMSAwIG9iago8PAovVGl0bGUgKEludm9pY2UgIzAwMSkKL0NyZWF0b3IgKE1vY2sgQmlsbGluZyBBUEkpCi9Qcm9kdWNlciAoTW9jayBCaWxsaW5nIEFQSSkKL0NyZWF0aW9uRGF0ZSAoRDoyMDI0MTIxNTAwMDAwMFopCj4+CmVuZG9iagoyIDAgb2JqCjw8Ci9UeXBlIC9DYXRhbG9nCi9QYWdlcyAzIDAgUgo+PgplbmRvYmoKMyAwIG9iago8PAovVHlwZSAvUGFnZXMKL0tpZHMgWzQgMCBSXQovQ291bnQgMQo+PgplbmRvYmoKNCAwIG9iago8PAovVHlwZSAvUGFnZQovUGFyZW50IDMgMCBSCi9NZWRpYUJveCBbMCAwIDYxMiA3OTJdCi9Db250ZW50cyA1IDAgUgo+PgplbmRvYmoKNSAwIG9iago8PAovTGVuZ3RoIDQ0Cj4+CnN0cmVhbQpCVApxCjcyIDcyMCA3MiA3MjAgcmUKUwpRCkJUCi9GMSAxMiBUZgo3MiA3MDAgVGQKKEludm9pY2UgIzAwMSkgVGoKRVQKZW5kc3RyZWFtCmVuZG9iago2IDAgb2JqCjw8Ci9UeXBlIC9Gb250Ci9TdWJ0eXBlIC9UeXBlMQovQmFzZUZvbnQgL0hlbHZldGljYQo+PgplbmRvYmoKeHJlZgowIDcKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAxNzQgMDAwMDAgbiAKMDAwMDAwMDIyMSAwMDAwMCBuIAowMDAwMDAwMjc4IDAwMDAwIG4gCjAwMDAwMDAzNzUgMDAwMDAgbiAKMDAwMDAwMDQ2OSAwMDAwMCBuIAp0cmFpbGVyCjw8Ci9TaXplIDcKL1Jvb3QgMiAwIFIKPj4Kc3RhcnR4cmVmCjU2NwolJUVPRgo=".to_string(),
 | 
			
		||||
            },
 | 
			
		||||
            Invoice {
 | 
			
		||||
                id: "inv_002".to_string(),
 | 
			
		||||
                date: "Nov 15, 2024".to_string(),
 | 
			
		||||
                description: "Business Pro - Monthly".to_string(),
 | 
			
		||||
                amount: 99.0,
 | 
			
		||||
                status: "Paid".to_string(),
 | 
			
		||||
                pdf_url: "data:application/pdf;base64,JVBERi0xLjQKJdPr6eEKMSAwIG9iago8PAovVGl0bGUgKEludm9pY2UgIzAwMikKL0NyZWF0b3IgKE1vY2sgQmlsbGluZyBBUEkpCi9Qcm9kdWNlciAoTW9jayBCaWxsaW5nIEFQSSkKL0NyZWF0aW9uRGF0ZSAoRDoyMDI0MTExNTAwMDAwMFopCj4+CmVuZG9iagoyIDAgb2JqCjw8Ci9UeXBlIC9DYXRhbG9nCi9QYWdlcyAzIDAgUgo+PgplbmRvYmoKMyAwIG9iago8PAovVHlwZSAvUGFnZXMKL0tpZHMgWzQgMCBSXQovQ291bnQgMQo+PgplbmRvYmoKNCAwIG9iago8PAovVHlwZSAvUGFnZQovUGFyZW50IDMgMCBSCi9NZWRpYUJveCBbMCAwIDYxMiA3OTJdCi9Db250ZW50cyA1IDAgUgo+PgplbmRvYmoKNSAwIG9iago8PAovTGVuZ3RoIDQ0Cj4+CnN0cmVhbQpCVApxCjcyIDcyMCA3MiA3MjAgcmUKUwpRCkJUCi9GMSAxMiBUZgo3MiA3MDAgVGQKKEludm9pY2UgIzAwMikgVGoKRVQKZW5kc3RyZWFtCmVuZG9iago2IDAgb2JqCjw8Ci9UeXBlIC9Gb250Ci9TdWJ0eXBlIC9UeXBlMQovQmFzZUZvbnQgL0hlbHZldGljYQo+PgplbmRvYmoKeHJlZgowIDcKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAxNzQgMDAwMDAgbiAKMDAwMDAwMDIyMSAwMDAwMCBuIAowMDAwMDAwMjc4IDAwMDAwIG4gCjAwMDAwMDAzNzUgMDAwMDAgbiAKMDAwMDAwMDQ2OSAwMDAwMCBuIAp0cmFpbGVyCjw8Ci9TaXplIDcKL1Jvb3QgMiAwIFIKPj4Kc3RhcnR4cmVmCjU2NwolJUVPRgo=".to_string(),
 | 
			
		||||
            },
 | 
			
		||||
            Invoice {
 | 
			
		||||
                id: "inv_003".to_string(),
 | 
			
		||||
                date: "Oct 15, 2024".to_string(),
 | 
			
		||||
                description: "Business Pro - Monthly".to_string(),
 | 
			
		||||
                amount: 99.0,
 | 
			
		||||
                status: "Paid".to_string(),
 | 
			
		||||
                pdf_url: "data:application/pdf;base64,JVBERi0xLjQKJdPr6eEKMSAwIG9iago8PAovVGl0bGUgKEludm9pY2UgIzAwMykKL0NyZWF0b3IgKE1vY2sgQmlsbGluZyBBUEkpCi9Qcm9kdWNlciAoTW9jayBCaWxsaW5nIEFQSSkKL0NyZWF0aW9uRGF0ZSAoRDoyMDI0MTAxNTAwMDAwMFopCj4+CmVuZG9iagoyIDAgb2JqCjw8Ci9UeXBlIC9DYXRhbG9nCi9QYWdlcyAzIDAgUgo+PgplbmRvYmoKMyAwIG9iago8PAovVHlwZSAvUGFnZXMKL0tpZHMgWzQgMCBSXQovQ291bnQgMQo+PgplbmRvYmoKNCAwIG9iago8PAovVHlwZSAvUGFnZQovUGFyZW50IDMgMCBSCi9NZWRpYUJveCBbMCAwIDYxMiA3OTJdCi9Db250ZW50cyA1IDAgUgo+PgplbmRvYmoKNSAwIG9iago8PAovTGVuZ3RoIDQ0Cj4+CnN0cmVhbQpCVApxCjcyIDcyMCA3MiA3MjAgcmUKUwpRCkJUCi9GMSAxMiBUZgo3MiA3MDAgVGQKKEludm9pY2UgIzAwMykgVGoKRVQKZW5kc3RyZWFtCmVuZG9iago2IDAgb2JqCjw8Ci9UeXBlIC9Gb250Ci9TdWJ0eXBlIC9UeXBlMQovQmFzZUZvbnQgL0hlbHZldGljYQo+PgplbmRvYmoKeHJlZgowIDcKMDAwMDAwMDAwMCA2NTUzNSBmIAowMDAwMDAwMDA5IDAwMDAwIG4gCjAwMDAwMDAxNzQgMDAwMDAgbiAKMDAwMDAwMDIyMSAwMDAwMCBuIAowMDAwMDAwMjc4IDAwMDAwIG4gCjAwMDAwMDAzNzUgMDAwMDAgbiAKMDAwMDAwMDQ2OSAwMDAwMCBuIAp0cmFpbGVyCjw8Ci9TaXplIDcKL1Jvb3QgMiAwIFIKPj4Kc3RhcnR4cmVmCjU2NwolJUVPRgo=".to_string(),
 | 
			
		||||
            },
 | 
			
		||||
            Invoice {
 | 
			
		||||
                id: "inv_004".to_string(),
 | 
			
		||||
                date: "Sep 15, 2024".to_string(),
 | 
			
		||||
                description: "Setup Fee".to_string(),
 | 
			
		||||
                amount: 50.0,
 | 
			
		||||
                status: "Paid".to_string(),
 | 
			
		||||
                pdf_url: "data:application/pdf;base64,JVBERi0xLjQKJdPr6eEKMSAwIG9iago8PAovVGl0bGUgKFNldHVwIEZlZSBJbnZvaWNlKQovQ3JlYXRvciAoTW9jayBCaWxsaW5nIEFQSSkKL1Byb2R1Y2VyIChNb2NrIEJpbGxpbmcgQVBJKQovQ3JlYXRpb25EYXRlIChEOjIwMjQwOTE1MDAwMDAwWikKPj4KZW5kb2JqCjIgMCBvYmoKPDwKL1R5cGUgL0NhdGFsb2cKL1BhZ2VzIDMgMCBSCj4+CmVuZG9iagozIDAgb2JqCjw8Ci9UeXBlIC9QYWdlcwovS2lkcyBbNCAwIFJdCi9Db3VudCAxCj4+CmVuZG9iago0IDAgb2JqCjw8Ci9UeXBlIC9QYWdlCi9QYXJlbnQgMyAwIFIKL01lZGlhQm94IFswIDAgNjEyIDc5Ml0KL0NvbnRlbnRzIDUgMCBSCj4+CmVuZG9iago1IDAgb2JqCjw8Ci9MZW5ndGggNDQKPj4Kc3RyZWFtCkJUCnEKNzIgNzIwIDcyIDcyMCByZQpTClEKQlQKL0YxIDEyIFRmCjcyIDcwMCBUZAooU2V0dXAgRmVlKSBUagpFVAplbmRzdHJlYW0KZW5kb2JqCjYgMCBvYmoKPDwKL1R5cGUgL0ZvbnQKL1N1YnR5cGUgL1R5cGUxCi9CYXNlRm9udCAvSGVsdmV0aWNhCj4+CmVuZG9iagp4cmVmCjAgNwowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwMDkgMDAwMDAgbiAKMDAwMDAwMDE3NCAwMDAwMCBuIAowMDAwMDAwMjIxIDAwMDAwIG4gCjAwMDAwMDAyNzggMDAwMDAgbiAKMDAwMDAwMDM3NSAwMDAwMCBuIAowMDAwMDAwNDY5IDAwMDAwIG4gCnRyYWlsZXIKPDwKL1NpemUgNwovUm9vdCAyIDAgUgo+PgpzdGFydHhyZWYKNTY3CiUlRU9GCg==".to_string(),
 | 
			
		||||
            },
 | 
			
		||||
        ];
 | 
			
		||||
 | 
			
		||||
        Self {
 | 
			
		||||
            current_subscription,
 | 
			
		||||
            available_plans,
 | 
			
		||||
            payment_methods,
 | 
			
		||||
            invoices,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Subscription methods
 | 
			
		||||
    pub async fn get_current_subscription(&self) -> Result<Subscription, String> {
 | 
			
		||||
        // Simulate API delay
 | 
			
		||||
        Ok(self.current_subscription.clone())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn get_available_plans(&self) -> Result<Vec<Plan>, String> {
 | 
			
		||||
        Ok(self.available_plans.clone())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn change_plan(&mut self, plan_id: &str) -> Result<Subscription, String> {
 | 
			
		||||
        if let Some(plan) = self.available_plans.iter().find(|p| p.id == plan_id) {
 | 
			
		||||
            self.current_subscription.plan = plan.clone();
 | 
			
		||||
            Ok(self.current_subscription.clone())
 | 
			
		||||
        } else {
 | 
			
		||||
            Err("Plan not found".to_string())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn cancel_subscription(&mut self) -> Result<String, String> {
 | 
			
		||||
        self.current_subscription.status = "cancelled".to_string();
 | 
			
		||||
        Ok("Subscription cancelled successfully".to_string())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Payment methods
 | 
			
		||||
    pub async fn get_payment_methods(&self) -> Result<Vec<PaymentMethod>, String> {
 | 
			
		||||
        Ok(self.payment_methods.clone())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn add_payment_method(&mut self, method_type: &str, last_four: &str) -> Result<PaymentMethod, String> {
 | 
			
		||||
        let new_method = PaymentMethod {
 | 
			
		||||
            id: format!("{}_{}", method_type.to_lowercase(), last_four),
 | 
			
		||||
            method_type: method_type.to_string(),
 | 
			
		||||
            last_four: last_four.to_string(),
 | 
			
		||||
            expires: if method_type == "Credit Card" { Some("12/28".to_string()) } else { None },
 | 
			
		||||
            is_primary: false,
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        self.payment_methods.push(new_method.clone());
 | 
			
		||||
        Ok(new_method)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn remove_payment_method(&mut self, method_id: &str) -> Result<String, String> {
 | 
			
		||||
        if let Some(pos) = self.payment_methods.iter().position(|m| m.id == method_id) {
 | 
			
		||||
            self.payment_methods.remove(pos);
 | 
			
		||||
            Ok("Payment method removed successfully".to_string())
 | 
			
		||||
        } else {
 | 
			
		||||
            Err("Payment method not found".to_string())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Invoices
 | 
			
		||||
    pub async fn get_invoices(&self) -> Result<Vec<Invoice>, String> {
 | 
			
		||||
        Ok(self.invoices.clone())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn download_invoice(&self, invoice_id: &str) -> Result<String, String> {
 | 
			
		||||
        if let Some(invoice) = self.invoices.iter().find(|i| i.id == invoice_id) {
 | 
			
		||||
            Ok(invoice.pdf_url.clone())
 | 
			
		||||
        } else {
 | 
			
		||||
            Err("Invoice not found".to_string())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								platform/src/services/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								platform/src/services/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
pub mod mock_billing_api;
 | 
			
		||||
pub mod company_service;
 | 
			
		||||
pub mod resident_service;
 | 
			
		||||
 | 
			
		||||
pub use mock_billing_api::*;
 | 
			
		||||
pub use company_service::*;
 | 
			
		||||
pub use resident_service::*;
 | 
			
		||||
							
								
								
									
										257
									
								
								platform/src/services/resident_service.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										257
									
								
								platform/src/services/resident_service.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,257 @@
 | 
			
		||||
use crate::models::company::{DigitalResident, DigitalResidentFormData, KycStatus};
 | 
			
		||||
use gloo::storage::{LocalStorage, Storage};
 | 
			
		||||
 | 
			
		||||
const RESIDENTS_STORAGE_KEY: &str = "freezone_residents";
 | 
			
		||||
const RESIDENT_REGISTRATIONS_STORAGE_KEY: &str = "freezone_resident_registrations";
 | 
			
		||||
const RESIDENT_FORM_KEY: &str = "freezone_resident_registration_form";
 | 
			
		||||
const FORM_EXPIRY_HOURS: i64 = 24;
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
 | 
			
		||||
pub struct ResidentRegistration {
 | 
			
		||||
    pub id: u32,
 | 
			
		||||
    pub full_name: String,
 | 
			
		||||
    pub email: String,
 | 
			
		||||
    pub status: ResidentRegistrationStatus,
 | 
			
		||||
    pub created_at: String,
 | 
			
		||||
    pub form_data: DigitalResidentFormData,
 | 
			
		||||
    pub current_step: u8,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, PartialEq, serde::Serialize, serde::Deserialize)]
 | 
			
		||||
pub enum ResidentRegistrationStatus {
 | 
			
		||||
    Draft,
 | 
			
		||||
    PendingPayment,
 | 
			
		||||
    PaymentFailed,
 | 
			
		||||
    PendingApproval,
 | 
			
		||||
    Approved,
 | 
			
		||||
    Rejected,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ResidentRegistrationStatus {
 | 
			
		||||
    pub fn to_string(&self) -> &'static str {
 | 
			
		||||
        match self {
 | 
			
		||||
            ResidentRegistrationStatus::Draft => "Draft",
 | 
			
		||||
            ResidentRegistrationStatus::PendingPayment => "Pending Payment",
 | 
			
		||||
            ResidentRegistrationStatus::PaymentFailed => "Payment Failed",
 | 
			
		||||
            ResidentRegistrationStatus::PendingApproval => "Pending Approval",
 | 
			
		||||
            ResidentRegistrationStatus::Approved => "Approved",
 | 
			
		||||
            ResidentRegistrationStatus::Rejected => "Rejected",
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    pub fn get_badge_class(&self) -> &'static str {
 | 
			
		||||
        match self {
 | 
			
		||||
            ResidentRegistrationStatus::Draft => "bg-secondary",
 | 
			
		||||
            ResidentRegistrationStatus::PendingPayment => "bg-warning",
 | 
			
		||||
            ResidentRegistrationStatus::PaymentFailed => "bg-danger",
 | 
			
		||||
            ResidentRegistrationStatus::PendingApproval => "bg-info",
 | 
			
		||||
            ResidentRegistrationStatus::Approved => "bg-success",
 | 
			
		||||
            ResidentRegistrationStatus::Rejected => "bg-danger",
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct ResidentService;
 | 
			
		||||
 | 
			
		||||
impl ResidentService {
 | 
			
		||||
    /// Get all residents from local storage
 | 
			
		||||
    pub fn get_residents() -> Vec<DigitalResident> {
 | 
			
		||||
        match LocalStorage::get::<Vec<DigitalResident>>(RESIDENTS_STORAGE_KEY) {
 | 
			
		||||
            Ok(residents) => residents,
 | 
			
		||||
            Err(_) => {
 | 
			
		||||
                // Initialize with empty list if not found
 | 
			
		||||
                let residents = Vec::new();
 | 
			
		||||
                let _ = LocalStorage::set(RESIDENTS_STORAGE_KEY, &residents);
 | 
			
		||||
                residents
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Save residents to local storage
 | 
			
		||||
    pub fn save_residents(residents: &[DigitalResident]) -> Result<(), String> {
 | 
			
		||||
        LocalStorage::set(RESIDENTS_STORAGE_KEY, residents)
 | 
			
		||||
            .map_err(|e| format!("Failed to save residents: {:?}", e))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Add a new resident
 | 
			
		||||
    pub fn add_resident(mut resident: DigitalResident) -> Result<DigitalResident, String> {
 | 
			
		||||
        let mut residents = Self::get_residents();
 | 
			
		||||
        
 | 
			
		||||
        // Generate new ID
 | 
			
		||||
        let max_id = residents.iter().map(|r| r.id).max().unwrap_or(0);
 | 
			
		||||
        resident.id = max_id + 1;
 | 
			
		||||
        
 | 
			
		||||
        residents.push(resident.clone());
 | 
			
		||||
        Self::save_residents(&residents)?;
 | 
			
		||||
        
 | 
			
		||||
        Ok(resident)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get all resident registrations from local storage
 | 
			
		||||
    pub fn get_resident_registrations() -> Vec<ResidentRegistration> {
 | 
			
		||||
        match LocalStorage::get::<Vec<ResidentRegistration>>(RESIDENT_REGISTRATIONS_STORAGE_KEY) {
 | 
			
		||||
            Ok(registrations) => registrations,
 | 
			
		||||
            Err(_) => {
 | 
			
		||||
                // Initialize with empty list if not found
 | 
			
		||||
                let registrations = Vec::new();
 | 
			
		||||
                let _ = LocalStorage::set(RESIDENT_REGISTRATIONS_STORAGE_KEY, ®istrations);
 | 
			
		||||
                registrations
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Save resident registrations to local storage
 | 
			
		||||
    pub fn save_resident_registrations(registrations: &[ResidentRegistration]) -> Result<(), String> {
 | 
			
		||||
        LocalStorage::set(RESIDENT_REGISTRATIONS_STORAGE_KEY, registrations)
 | 
			
		||||
            .map_err(|e| format!("Failed to save resident registrations: {:?}", e))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Add or update a resident registration
 | 
			
		||||
    pub fn save_resident_registration(mut registration: ResidentRegistration) -> Result<ResidentRegistration, String> {
 | 
			
		||||
        let mut registrations = Self::get_resident_registrations();
 | 
			
		||||
        
 | 
			
		||||
        if registration.id == 0 {
 | 
			
		||||
            // Generate new ID for new registration
 | 
			
		||||
            let max_id = registrations.iter().map(|r| r.id).max().unwrap_or(0);
 | 
			
		||||
            registration.id = max_id + 1;
 | 
			
		||||
            registrations.push(registration.clone());
 | 
			
		||||
        } else {
 | 
			
		||||
            // Update existing registration
 | 
			
		||||
            if let Some(existing) = registrations.iter_mut().find(|r| r.id == registration.id) {
 | 
			
		||||
                *existing = registration.clone();
 | 
			
		||||
            } else {
 | 
			
		||||
                return Err("Registration not found".to_string());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        Self::save_resident_registrations(®istrations)?;
 | 
			
		||||
        Ok(registration)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Save registration form data with expiration
 | 
			
		||||
    pub fn save_resident_registration_form(form_data: &DigitalResidentFormData, current_step: u8) -> Result<(), String> {
 | 
			
		||||
        let now = js_sys::Date::now() as i64;
 | 
			
		||||
        let expires_at = now + (FORM_EXPIRY_HOURS * 60 * 60 * 1000);
 | 
			
		||||
        
 | 
			
		||||
        let saved_form = SavedResidentRegistrationForm {
 | 
			
		||||
            form_data: form_data.clone(),
 | 
			
		||||
            current_step,
 | 
			
		||||
            saved_at: now,
 | 
			
		||||
            expires_at,
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        LocalStorage::set(RESIDENT_FORM_KEY, &saved_form)
 | 
			
		||||
            .map_err(|e| format!("Failed to save form: {:?}", e))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Load registration form data if not expired
 | 
			
		||||
    pub fn load_resident_registration_form() -> Option<(DigitalResidentFormData, u8)> {
 | 
			
		||||
        match LocalStorage::get::<SavedResidentRegistrationForm>(RESIDENT_FORM_KEY) {
 | 
			
		||||
            Ok(saved_form) => {
 | 
			
		||||
                let now = js_sys::Date::now() as i64;
 | 
			
		||||
                if now < saved_form.expires_at {
 | 
			
		||||
                    Some((saved_form.form_data, saved_form.current_step))
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Form expired, remove it
 | 
			
		||||
                    let _ = LocalStorage::delete(RESIDENT_FORM_KEY);
 | 
			
		||||
                    None
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Err(_) => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Clear saved registration form
 | 
			
		||||
    pub fn clear_resident_registration_form() -> Result<(), String> {
 | 
			
		||||
        LocalStorage::delete(RESIDENT_FORM_KEY);
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Create a resident from form data
 | 
			
		||||
    pub fn create_resident_from_form(form_data: &DigitalResidentFormData) -> Result<DigitalResident, String> {
 | 
			
		||||
        let now = js_sys::Date::new_0();
 | 
			
		||||
        let registration_date = format!(
 | 
			
		||||
            "{:04}-{:02}-{:02}",
 | 
			
		||||
            now.get_full_year(),
 | 
			
		||||
            now.get_month() + 1,
 | 
			
		||||
            now.get_date()
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        let resident = DigitalResident {
 | 
			
		||||
            id: 0, // Will be set by add_resident
 | 
			
		||||
            full_name: form_data.full_name.clone(),
 | 
			
		||||
            email: form_data.email.clone(),
 | 
			
		||||
            phone: form_data.phone.clone(),
 | 
			
		||||
            date_of_birth: form_data.date_of_birth.clone(),
 | 
			
		||||
            nationality: form_data.nationality.clone(),
 | 
			
		||||
            passport_number: form_data.passport_number.clone(),
 | 
			
		||||
            passport_expiry: form_data.passport_expiry.clone(),
 | 
			
		||||
            current_address: form_data.current_address.clone(),
 | 
			
		||||
            city: form_data.city.clone(),
 | 
			
		||||
            country: form_data.country.clone(),
 | 
			
		||||
            postal_code: form_data.postal_code.clone(),
 | 
			
		||||
            occupation: form_data.occupation.clone(),
 | 
			
		||||
            employer: form_data.employer.clone(),
 | 
			
		||||
            annual_income: form_data.annual_income.clone(),
 | 
			
		||||
            education_level: form_data.education_level.clone(),
 | 
			
		||||
            selected_services: form_data.requested_services.clone(),
 | 
			
		||||
            payment_plan: form_data.payment_plan.clone(),
 | 
			
		||||
            registration_date,
 | 
			
		||||
            status: crate::models::company::ResidentStatus::Pending,
 | 
			
		||||
            kyc_documents_uploaded: false, // Will be updated when documents are uploaded
 | 
			
		||||
            kyc_status: KycStatus::NotStarted,
 | 
			
		||||
            public_key: form_data.public_key.clone(),
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        Self::add_resident(resident)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Validate form data for a specific step (simplified 2-step form)
 | 
			
		||||
    pub fn validate_resident_step(form_data: &DigitalResidentFormData, step: u8) -> crate::models::ValidationResult {
 | 
			
		||||
        let mut errors = Vec::new();
 | 
			
		||||
        
 | 
			
		||||
        match step {
 | 
			
		||||
            1 => {
 | 
			
		||||
                // Step 1: Personal Information & KYC (simplified - only name, email, and terms required)
 | 
			
		||||
                if form_data.full_name.trim().is_empty() {
 | 
			
		||||
                    errors.push("Full name is required".to_string());
 | 
			
		||||
                }
 | 
			
		||||
                if form_data.email.trim().is_empty() {
 | 
			
		||||
                    errors.push("Email is required".to_string());
 | 
			
		||||
                } else if !Self::is_valid_email(&form_data.email) {
 | 
			
		||||
                    errors.push("Please enter a valid email address".to_string());
 | 
			
		||||
                }
 | 
			
		||||
                if !form_data.legal_agreements.terms {
 | 
			
		||||
                    errors.push("You must agree to the Terms of Service and Privacy Policy".to_string());
 | 
			
		||||
                }
 | 
			
		||||
                // Note: KYC verification is handled separately via button click
 | 
			
		||||
            }
 | 
			
		||||
            2 => {
 | 
			
		||||
                // Step 2: Payment only (no additional agreements needed)
 | 
			
		||||
                // Payment validation will be handled by Stripe
 | 
			
		||||
            }
 | 
			
		||||
            _ => {
 | 
			
		||||
                errors.push("Invalid step".to_string());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if errors.is_empty() {
 | 
			
		||||
            crate::models::ValidationResult { is_valid: true, errors: Vec::new() }
 | 
			
		||||
        } else {
 | 
			
		||||
            crate::models::ValidationResult { is_valid: false, errors }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Simple email validation
 | 
			
		||||
    fn is_valid_email(email: &str) -> bool {
 | 
			
		||||
        email.contains('@') && email.contains('.') && email.len() > 5
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
 | 
			
		||||
struct SavedResidentRegistrationForm {
 | 
			
		||||
    form_data: DigitalResidentFormData,
 | 
			
		||||
    current_step: u8,
 | 
			
		||||
    saved_at: i64,
 | 
			
		||||
    expires_at: i64,
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user