updates to mock content and contract view implementation

This commit is contained in:
Timur Gordon 2025-04-23 03:52:11 +02:00
parent 6060831f61
commit b56f1cbc30
9 changed files with 1453 additions and 644 deletions

View File

@ -447,174 +447,232 @@ impl AssetController {
let now = Utc::now();
let mut assets = Vec::new();
// Create NFT asset
let mut nft = Asset {
id: "asset-nft12345".to_string(),
name: "CryptoKitty #12345".to_string(),
description: "A unique digital cat collectible".to_string(),
asset_type: AssetType::NFT,
status: AssetStatus::Active,
owner_id: "user-123".to_string(),
owner_name: "John Doe".to_string(),
created_at: now - Duration::days(30),
updated_at: now,
blockchain_info: None,
current_valuation: Some(600.0),
valuation_currency: Some("USD".to_string()),
valuation_date: Some(now - Duration::days(1)),
valuation_history: Vec::new(),
transaction_history: Vec::new(),
metadata: serde_json::json!({}),
image_url: Some("https://example.com/cryptokitty12345.png".to_string()),
external_url: Some("https://www.cryptokitties.co/kitty/12345".to_string()),
};
nft.add_blockchain_info(BlockchainInfo {
blockchain: "Ethereum".to_string(),
token_id: "12345".to_string(),
contract_address: "0x06012c8cf97BEaD5deAe237070F9587f8E7A266d".to_string(),
owner_address: "0xb794f5ea0ba39494ce839613fffba74279579268".to_string(),
transaction_hash: Some("0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string()),
block_number: Some(12345678),
timestamp: Some(now - Duration::days(30)),
});
nft.add_valuation(500.0, "USD", "OpenSea", Some("Initial valuation".to_string()));
nft.add_valuation(550.0, "USD", "OpenSea", Some("Market increase".to_string()));
nft.add_valuation(600.0, "USD", "OpenSea", None);
nft.add_transaction(
"Purchase",
Some("0xa1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2".to_string()),
Some("0xb794f5ea0ba39494ce839613fffba74279579268".to_string()),
Some(500.0),
Some("USD".to_string()),
Some("0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string()),
None,
);
assets.push(nft);
// Create Real Estate asset
let mut real_estate = Asset {
id: "asset-realestate789".to_string(),
name: "Apartment 123, New York".to_string(),
description: "A luxury apartment in downtown Manhattan".to_string(),
// Create Tokenized Real Estate asset
let mut zanzibar_resort = Asset {
id: "asset-zanzibar-resort".to_string(),
name: "Zanzibar Coastal Resort".to_string(),
description: "A tokenized luxury eco-resort on the eastern coast of Zanzibar with 20 villas and sustainable energy infrastructure".to_string(),
asset_type: AssetType::RealEstate,
status: AssetStatus::Active,
owner_id: "user-456".to_string(),
owner_name: "Jane Smith".to_string(),
created_at: now - Duration::days(60),
updated_at: now,
owner_id: "entity-oceanview-holdings".to_string(),
owner_name: "OceanView Holdings Ltd.".to_string(),
created_at: now - Duration::days(120),
updated_at: now - Duration::days(5),
blockchain_info: None,
current_valuation: Some(1050000.0),
current_valuation: Some(3750000.0),
valuation_currency: Some("USD".to_string()),
valuation_date: Some(now - Duration::days(5)),
valuation_date: Some(now - Duration::days(15)),
valuation_history: Vec::new(),
transaction_history: Vec::new(),
metadata: serde_json::json!({}),
image_url: Some("https://example.com/apartment123.jpg".to_string()),
external_url: None,
metadata: serde_json::json!({
"location": "East Coast, Zanzibar",
"property_size": "5.2 hectares",
"buildings": 22,
"tokenization_date": (now - Duration::days(120)).to_rfc3339(),
"total_tokens": 10000,
"token_price": 375.0
}),
image_url: Some("https://example.com/zanzibar_resort.jpg".to_string()),
external_url: Some("https://oceanviewholdings.zaz/resort".to_string()),
};
real_estate.add_blockchain_info(BlockchainInfo {
zanzibar_resort.add_blockchain_info(BlockchainInfo {
blockchain: "Ethereum".to_string(),
token_id: "RE789".to_string(),
token_id: "ZRESORT".to_string(),
contract_address: "0x123456789abcdef123456789abcdef12345678ab".to_string(),
owner_address: "0xc3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4".to_string(),
transaction_hash: Some("0xabcdef123456789abcdef123456789abcdef123456789abcdef123456789abcd".to_string()),
block_number: Some(9876543),
timestamp: Some(now - Duration::days(60)),
timestamp: Some(now - Duration::days(120)),
});
real_estate.add_valuation(1000000.0, "USD", "Property Appraisal", Some("Initial valuation".to_string()));
real_estate.add_valuation(1050000.0, "USD", "Property Appraisal", Some("Annual update".to_string()));
zanzibar_resort.add_valuation(3500000.0, "USD", "ZAZ Property Registry", Some("Initial tokenization valuation".to_string()));
zanzibar_resort.add_valuation(3650000.0, "USD", "International Property Appraisers", Some("Independent third-party valuation".to_string()));
zanzibar_resort.add_valuation(3750000.0, "USD", "ZAZ Property Registry", Some("Updated valuation after infrastructure improvements".to_string()));
real_estate.add_transaction(
zanzibar_resort.add_transaction(
"Tokenization",
None,
Some("0xc3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4".to_string()),
Some(1000000.0),
Some(3500000.0),
Some("USD".to_string()),
Some("0xabcdef123456789abcdef123456789abcdef123456789abcdef123456789abcd".to_string()),
Some("Initial tokenization of property".to_string()),
Some("Initial property tokenization under ZAZ Property Registry".to_string()),
);
assets.push(real_estate);
zanzibar_resort.add_transaction(
"Token Sale",
Some("0xc3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4".to_string()),
Some("0x7a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b".to_string()),
Some(375000.0),
Some("USD".to_string()),
Some("0xdef123456789abcdef123456789abcdef123456789abcdef123456789abcdef".to_string()),
Some("Sale of 10% ownership tokens to Zanzibar Investment Collective".to_string()),
);
// Create Token asset
let mut token = Asset {
id: "asset-token456".to_string(),
name: "OurWorld Token".to_string(),
description: "Utility token for the OurWorld platform".to_string(),
assets.push(zanzibar_resort);
// Create ZAZ Governance Token
let mut zaz_token = Asset {
id: "asset-zaz-governance".to_string(),
name: "ZAZ Governance Token".to_string(),
description: "Official governance token of the Zanzibar Autonomous Zone, used for voting on proposals and zone-wide decisions".to_string(),
asset_type: AssetType::Token,
status: AssetStatus::Active,
owner_id: "user-789".to_string(),
owner_name: "Alice Johnson".to_string(),
created_at: now - Duration::days(90),
updated_at: now,
owner_id: "entity-zaz-foundation".to_string(),
owner_name: "Zanzibar Autonomous Zone Foundation".to_string(),
created_at: now - Duration::days(365),
updated_at: now - Duration::days(2),
blockchain_info: None,
current_valuation: Some(110000.0),
valuation_currency: Some("TFT".to_string()),
valuation_date: Some(now - Duration::days(2)),
current_valuation: Some(12500000.0),
valuation_currency: Some("USD".to_string()),
valuation_date: Some(now - Duration::days(3)),
valuation_history: Vec::new(),
transaction_history: Vec::new(),
metadata: serde_json::json!({}),
image_url: Some("https://example.com/ourworld_token.png".to_string()),
external_url: Some("https://ourworld.tf/token".to_string()),
metadata: serde_json::json!({
"total_supply": 10000000,
"circulating_supply": 7500000,
"governance_weight": 1.0,
"minimum_holding_for_proposals": 10000,
"launch_date": (now - Duration::days(365)).to_rfc3339()
}),
image_url: Some("https://example.com/zaz_token.png".to_string()),
external_url: Some("https://governance.zaz/token".to_string()),
};
token.add_blockchain_info(BlockchainInfo {
zaz_token.add_blockchain_info(BlockchainInfo {
blockchain: "ThreeFold".to_string(),
token_id: "TFT".to_string(),
token_id: "ZAZT".to_string(),
contract_address: "0xf6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1".to_string(),
owner_address: "0xe5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6".to_string(),
transaction_hash: None,
block_number: None,
timestamp: Some(now - Duration::days(90)),
timestamp: Some(now - Duration::days(365)),
});
token.add_valuation(100000.0, "TFT", "ThreeFold Exchange", None);
token.add_valuation(120000.0, "TFT", "ThreeFold Exchange", None);
token.add_valuation(110000.0, "TFT", "ThreeFold Exchange", None);
zaz_token.add_valuation(8000000.0, "USD", "ZAZ Token Exchange", Some("Initial valuation at launch".to_string()));
zaz_token.add_valuation(10500000.0, "USD", "ZAZ Token Exchange", Some("Valuation after successful governance implementation".to_string()));
zaz_token.add_valuation(12500000.0, "USD", "ZAZ Token Exchange", Some("Current market valuation".to_string()));
token.add_transaction(
"Transfer",
Some("0xd4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5".to_string()),
zaz_token.add_transaction(
"Distribution",
Some("0xe5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6".to_string()),
Some(100000.0),
Some("TFT".to_string()),
None,
Some("Initial allocation".to_string()),
Some("0x9a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b".to_string()),
Some(1000000.0),
Some("ZAZT".to_string()),
Some("0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string()),
Some("Initial token distribution to founding members".to_string()),
);
assets.push(token);
zaz_token.add_transaction(
"Distribution",
Some("0xe5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6".to_string()),
Some("0x8b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c".to_string()),
Some(2500000.0),
Some("ZAZT".to_string()),
Some("0x234567890abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string()),
Some("Public token sale for zone participants".to_string()),
);
// Create Intellectual Property asset
let mut ip = Asset {
id: "asset-ip987654".to_string(),
name: "Patent #987654".to_string(),
description: "A patent for a new renewable energy technology".to_string(),
asset_type: AssetType::IntellectualProperty,
assets.push(zaz_token);
// Create Spice Trade Venture Shares
let mut spice_trade_shares = Asset {
id: "asset-spice-trade-shares".to_string(),
name: "Zanzibar Spice Trade Ventures".to_string(),
description: "Equity shares in Zanzibar Spice Trade Ventures, a leading exporter of organic spices from the Zanzibar region".to_string(),
asset_type: AssetType::Share,
status: AssetStatus::Active,
owner_id: "user-321".to_string(),
owner_name: "Bob Williams".to_string(),
created_at: now - Duration::days(120),
updated_at: now,
owner_id: "entity-spice-trade".to_string(),
owner_name: "Zanzibar Spice Trade Ventures Ltd.".to_string(),
created_at: now - Duration::days(180),
updated_at: now - Duration::days(7),
blockchain_info: None,
current_valuation: Some(300000.0),
current_valuation: Some(4250000.0),
valuation_currency: Some("USD".to_string()),
valuation_date: Some(now - Duration::days(10)),
valuation_date: Some(now - Duration::days(7)),
valuation_history: Vec::new(),
transaction_history: Vec::new(),
metadata: serde_json::json!({}),
image_url: None,
external_url: None,
metadata: serde_json::json!({
"total_shares": 1000000,
"share_type": "Common",
"business_sector": "Agriculture & Export",
"dividend_yield": 5.2,
"last_dividend_date": (now - Duration::days(30)).to_rfc3339(),
"incorporation_date": (now - Duration::days(180)).to_rfc3339()
}),
image_url: Some("https://example.com/spice_trade_logo.png".to_string()),
external_url: Some("https://spicetrade.zaz".to_string()),
};
ip.add_blockchain_info(BlockchainInfo {
spice_trade_shares.add_blockchain_info(BlockchainInfo {
blockchain: "Ethereum".to_string(),
token_id: "SPICE".to_string(),
contract_address: "0x3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4".to_string(),
owner_address: "0x6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b".to_string(),
transaction_hash: Some("0x789abcdef123456789abcdef123456789abcdef123456789abcdef123456789a".to_string()),
block_number: Some(7654321),
timestamp: Some(now - Duration::days(180)),
});
spice_trade_shares.add_valuation(3800000.0, "USD", "ZAZ Business Registry", Some("Initial company valuation at incorporation".to_string()));
spice_trade_shares.add_valuation(4000000.0, "USD", "ZAZ Business Registry", Some("Valuation after first export contracts".to_string()));
spice_trade_shares.add_valuation(4250000.0, "USD", "ZAZ Business Registry", Some("Current valuation after expansion to European markets".to_string()));
spice_trade_shares.add_transaction(
"Share Issuance",
None,
Some("0x6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b".to_string()),
Some(3800000.0),
Some("USD".to_string()),
Some("0x789abcdef123456789abcdef123456789abcdef123456789abcdef123456789a".to_string()),
Some("Initial share issuance at company formation".to_string()),
);
spice_trade_shares.add_transaction(
"Share Transfer",
Some("0x6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b".to_string()),
Some("0x7b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c".to_string()),
Some(950000.0),
Some("USD".to_string()),
Some("0x89abcdef123456789abcdef123456789abcdef123456789abcdef123456789ab".to_string()),
Some("Sale of 25% equity to East African Growth Partners".to_string()),
);
assets.push(spice_trade_shares);
// Create Sustainable Energy Patent
let mut tidal_energy_patent = Asset {
id: "asset-tidal-energy-patent".to_string(),
name: "Zanzibar Tidal Energy System Patent".to_string(),
description: "Patent for an innovative tidal energy harvesting system designed specifically for the coastal conditions of Zanzibar".to_string(),
asset_type: AssetType::IntellectualProperty,
status: AssetStatus::Active,
owner_id: "entity-zaz-energy-innovations".to_string(),
owner_name: "ZAZ Energy Innovations".to_string(),
created_at: now - Duration::days(210),
updated_at: now - Duration::days(30),
blockchain_info: None,
current_valuation: Some(2800000.0),
valuation_currency: Some("USD".to_string()),
valuation_date: Some(now - Duration::days(30)),
valuation_history: Vec::new(),
transaction_history: Vec::new(),
metadata: serde_json::json!({
"patent_number": "ZAZ-PAT-2024-0142",
"filing_date": (now - Duration::days(210)).to_rfc3339(),
"grant_date": (now - Duration::days(120)).to_rfc3339(),
"patent_type": "Utility",
"jurisdiction": "Zanzibar Autonomous Zone",
"inventors": ["Dr. Amina Juma", "Eng. Ibrahim Hassan", "Dr. Sarah Mbeki"]
}),
image_url: Some("https://example.com/tidal_energy_diagram.png".to_string()),
external_url: Some("https://patents.zaz/ZAZ-PAT-2024-0142".to_string()),
};
tidal_energy_patent.add_blockchain_info(BlockchainInfo {
blockchain: "Polygon".to_string(),
token_id: "IP987654".to_string(),
token_id: "TIDALIP".to_string(),
contract_address: "0x2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3".to_string(),
owner_address: "0x4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f".to_string(),
transaction_hash: Some("0x56789abcdef123456789abcdef123456789abcdef123456789abcdef12345678".to_string()),
@ -622,69 +680,96 @@ impl AssetController {
timestamp: Some(now - Duration::days(120)),
});
ip.add_valuation(250000.0, "USD", "Patent Valuation Service", Some("Initial valuation".to_string()));
ip.add_valuation(300000.0, "USD", "Patent Valuation Service", Some("After successful testing".to_string()));
tidal_energy_patent.add_valuation(1500000.0, "USD", "ZAZ IP Registry", Some("Initial patent valuation upon filing".to_string()));
tidal_energy_patent.add_valuation(2200000.0, "USD", "ZAZ IP Registry", Some("Valuation after successful prototype testing".to_string()));
tidal_energy_patent.add_valuation(2800000.0, "USD", "ZAZ IP Registry", Some("Current valuation after pilot implementation".to_string()));
ip.add_transaction(
tidal_energy_patent.add_transaction(
"Registration",
None,
Some("0x4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f".to_string()),
Some(1500000.0),
Some("USD".to_string()),
Some("0x56789abcdef123456789abcdef123456789abcdef123456789abcdef12345678".to_string()),
Some("Initial patent registration and tokenization".to_string()),
);
tidal_energy_patent.add_transaction(
"Licensing",
Some("0x4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f".to_string()),
Some("0x5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a".to_string()),
Some(50000.0),
Some(450000.0),
Some("USD".to_string()),
Some("0x6789abcdef123456789abcdef123456789abcdef123456789abcdef123456789".to_string()),
Some("Annual licensing fee".to_string()),
Some("Licensing agreement with Coastal Energy Solutions".to_string()),
);
assets.push(ip);
assets.push(tidal_energy_patent);
// Create Share asset
let mut share = Asset {
id: "asset-share123".to_string(),
name: "OurWorld Inc. Shares".to_string(),
description: "Equity shares in OurWorld Inc.".to_string(),
asset_type: AssetType::Share,
// Create Digital Art NFT
let mut zanzibar_heritage_nft = Asset {
id: "asset-heritage-nft".to_string(),
name: "Zanzibar Heritage Collection #1".to_string(),
description: "Limited edition digital art NFT showcasing Zanzibar's cultural heritage, created by renowned local artist Fatma Busaidy".to_string(),
asset_type: AssetType::NFT,
status: AssetStatus::Active,
owner_id: "user-654".to_string(),
owner_name: "Charlie Brown".to_string(),
created_at: now - Duration::days(150),
updated_at: now,
owner_id: "entity-zaz-digital-arts".to_string(),
owner_name: "ZAZ Digital Arts Collective".to_string(),
created_at: now - Duration::days(90),
updated_at: now - Duration::days(10),
blockchain_info: None,
current_valuation: Some(1300.0),
current_valuation: Some(15000.0),
valuation_currency: Some("USD".to_string()),
valuation_date: Some(now - Duration::days(3)),
valuation_date: Some(now - Duration::days(10)),
valuation_history: Vec::new(),
transaction_history: Vec::new(),
metadata: serde_json::json!({}),
image_url: None,
external_url: Some("https://ourworld.tf/shares".to_string()),
metadata: serde_json::json!({
"artist": "Fatma Busaidy",
"edition": "1 of 10",
"medium": "Digital Mixed Media",
"dimensions": "4000x3000 px",
"creation_date": (now - Duration::days(95)).to_rfc3339(),
"authenticity_certificate": "ZAZ-ART-CERT-2024-089"
}),
image_url: Some("https://example.com/zanzibar_heritage_nft.jpg".to_string()),
external_url: Some("https://digitalarts.zaz/collections/heritage/1".to_string()),
};
share.add_blockchain_info(BlockchainInfo {
zanzibar_heritage_nft.add_blockchain_info(BlockchainInfo {
blockchain: "Ethereum".to_string(),
token_id: "OWSHARE".to_string(),
contract_address: "0x3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4".to_string(),
owner_address: "0x6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b".to_string(),
transaction_hash: Some("0x789abcdef123456789abcdef123456789abcdef123456789abcdef123456789a".to_string()),
block_number: Some(7654321),
timestamp: Some(now - Duration::days(150)),
token_id: "HERITAGE1".to_string(),
contract_address: "0x06012c8cf97BEaD5deAe237070F9587f8E7A266d".to_string(),
owner_address: "0xb794f5ea0ba39494ce839613fffba74279579268".to_string(),
transaction_hash: Some("0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string()),
block_number: Some(12345678),
timestamp: Some(now - Duration::days(90)),
});
share.add_valuation(1000.0, "USD", "Stock Exchange", None);
share.add_valuation(1200.0, "USD", "Stock Exchange", None);
share.add_valuation(1150.0, "USD", "Stock Exchange", None);
share.add_valuation(1300.0, "USD", "Stock Exchange", None);
zanzibar_heritage_nft.add_valuation(12000.0, "USD", "ZAZ NFT Marketplace", Some("Initial offering price".to_string()));
zanzibar_heritage_nft.add_valuation(13500.0, "USD", "ZAZ NFT Marketplace", Some("Valuation after artist exhibition".to_string()));
zanzibar_heritage_nft.add_valuation(15000.0, "USD", "ZAZ NFT Marketplace", Some("Current market valuation".to_string()));
share.add_transaction(
"Purchase",
Some("0x7b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c".to_string()),
Some("0x6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b".to_string()),
Some(1000.0),
Some("USD".to_string()),
Some("0x89abcdef123456789abcdef123456789abcdef123456789abcdef123456789ab".to_string()),
zanzibar_heritage_nft.add_transaction(
"Minting",
None,
Some("0xb794f5ea0ba39494ce839613fffba74279579268".to_string()),
Some(0.0),
Some("ETH".to_string()),
Some("0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string()),
Some("Initial NFT minting by artist".to_string()),
);
assets.push(share);
zanzibar_heritage_nft.add_transaction(
"Sale",
Some("0xb794f5ea0ba39494ce839613fffba74279579268".to_string()),
Some("0xa1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2".to_string()),
Some(12000.0),
Some("USD".to_string()),
Some("0x234567890abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string()),
Some("Primary sale to ZAZ Digital Arts Collective".to_string()),
);
assets.push(zanzibar_heritage_nft);
assets
}

View File

@ -1,9 +1,10 @@
use actix_web::{web, HttpResponse, Result};
use actix_web::{web, HttpResponse, Result, Error};
use tera::{Context, Tera};
use chrono::{Utc, Duration};
use serde::Deserialize;
use serde_json::json;
use crate::models::contract::{Contract, ContractStatus, ContractType, ContractStatistics, SignerStatus};
use crate::models::contract::{Contract, ContractStatus, ContractType, ContractStatistics, ContractSigner, ContractRevision, SignerStatus};
use crate::utils::render_template;
#[derive(Debug, Deserialize)]
@ -24,7 +25,7 @@ pub struct ContractController;
impl ContractController {
// Display the contracts dashboard
pub async fn index(tmpl: web::Data<Tera>) -> Result<HttpResponse> {
pub async fn index(tmpl: web::Data<Tera>) -> Result<HttpResponse, Error> {
let mut context = Context::new();
let contracts = Self::get_mock_contracts();
@ -67,7 +68,7 @@ impl ContractController {
}
// Display the list of all contracts
pub async fn list(tmpl: web::Data<Tera>) -> Result<HttpResponse> {
pub async fn list(tmpl: web::Data<Tera>) -> Result<HttpResponse, Error> {
let mut context = Context::new();
let contracts = Self::get_mock_contracts();
@ -86,7 +87,7 @@ impl ContractController {
}
// Display the list of user's contracts
pub async fn my_contracts(tmpl: web::Data<Tera>) -> Result<HttpResponse> {
pub async fn my_contracts(tmpl: web::Data<Tera>) -> Result<HttpResponse, Error> {
let mut context = Context::new();
let contracts = Self::get_mock_contracts();
@ -104,7 +105,7 @@ impl ContractController {
}
// Display a specific contract
pub async fn detail(tmpl: web::Data<Tera>, path: web::Path<String>) -> Result<HttpResponse> {
pub async fn detail(tmpl: web::Data<Tera>, path: web::Path<String>) -> Result<HttpResponse, Error> {
let contract_id = path.into_inner();
let mut context = Context::new();
@ -113,26 +114,39 @@ impl ContractController {
// Find the contract by ID
let contracts = Self::get_mock_contracts();
let contract = contracts.iter().find(|c| c.id == contract_id);
match contract {
Some(contract) => {
// Convert contract to JSON
let contract_json = Self::contract_to_json(contract);
context.insert("contract", &contract_json);
context.insert("user_has_signed", &false); // Mock data
render_template(&tmpl, "contracts/contract_detail.html", &context)
},
None => {
Ok(HttpResponse::NotFound().finish())
}
}
// For demo purposes, if the ID doesn't match exactly, just show the first contract
// In a real app, we would return a 404 if the contract is not found
let contract = if let Some(found) = contracts.iter().find(|c| c.id == contract_id) {
found
} else {
// For demo, just use the first contract
contracts.first().unwrap()
};
// Convert contract to JSON
let contract_json = Self::contract_to_json(contract);
// Add contract to context
context.insert("contract", &contract_json);
// Count signed signers for the template
let signed_signers = contract.signers.iter().filter(|s| s.status == SignerStatus::Signed).count();
context.insert("signed_signers", &signed_signers);
// Count pending signers for the template
let pending_signers = contract.signers.iter().filter(|s| s.status == SignerStatus::Pending).count();
context.insert("pending_signers", &pending_signers);
// For demo purposes, set user_has_signed to false
// In a real app, we would check if the current user has already signed
context.insert("user_has_signed", &false);
render_template(&tmpl, "contracts/contract_detail.html", &context)
}
// Display the create contract form
pub async fn create_form(tmpl: web::Data<Tera>) -> Result<HttpResponse> {
pub async fn create_form(tmpl: web::Data<Tera>) -> Result<HttpResponse, Error> {
let mut context = Context::new();
// Add active_page for navigation highlighting
@ -156,7 +170,7 @@ impl ContractController {
pub async fn create(
_tmpl: web::Data<Tera>,
_form: web::Form<ContractForm>,
) -> Result<HttpResponse> {
) -> Result<HttpResponse, Error> {
// In a real application, we would save the contract to the database
// For now, we'll just redirect to the contracts list
@ -167,14 +181,22 @@ impl ContractController {
fn contract_to_json(contract: &Contract) -> serde_json::Map<String, serde_json::Value> {
let mut map = serde_json::Map::new();
// Basic contract info
map.insert("id".to_string(), serde_json::Value::String(contract.id.clone()));
map.insert("title".to_string(), serde_json::Value::String(contract.title.clone()));
map.insert("description".to_string(), serde_json::Value::String(contract.description.clone()));
map.insert("status".to_string(), serde_json::Value::String(format!("{:?}", contract.status)));
map.insert("contract_type".to_string(), serde_json::Value::String(format!("{:?}", contract.contract_type)));
map.insert("status".to_string(), serde_json::Value::String(contract.status.as_str().to_string()));
map.insert("contract_type".to_string(), serde_json::Value::String(contract.contract_type.as_str().to_string()));
map.insert("created_by".to_string(), serde_json::Value::String(contract.created_by.clone()));
map.insert("created_at".to_string(), serde_json::Value::String(contract.created_at.format("%Y-%m-%d").to_string()));
map.insert("updated_at".to_string(), serde_json::Value::String(contract.updated_at.format("%Y-%m-%d").to_string()));
map.insert("created_by".to_string(), serde_json::Value::String("John Doe".to_string())); // Mock data
// Organization info
if let Some(org) = &contract.organization_id {
map.insert("organization".to_string(), serde_json::Value::String(org.clone()));
} else {
map.insert("organization".to_string(), serde_json::Value::Null);
}
// Add signers
let signers: Vec<serde_json::Value> = contract.signers.iter()
@ -183,10 +205,23 @@ impl ContractController {
signer_map.insert("id".to_string(), serde_json::Value::String(s.id.clone()));
signer_map.insert("name".to_string(), serde_json::Value::String(s.name.clone()));
signer_map.insert("email".to_string(), serde_json::Value::String(s.email.clone()));
signer_map.insert("status".to_string(), serde_json::Value::String(format!("{:?}", s.status)));
signer_map.insert("status".to_string(), serde_json::Value::String(s.status.as_str().to_string()));
if let Some(signed_at) = s.signed_at {
signer_map.insert("signed_at".to_string(), serde_json::Value::String(signed_at.format("%Y-%m-%d").to_string()));
} else {
// For display purposes, add a placeholder date for pending signers
if s.status == SignerStatus::Pending {
signer_map.insert("signed_at".to_string(), serde_json::Value::String("Pending".to_string()));
} else if s.status == SignerStatus::Rejected {
signer_map.insert("signed_at".to_string(), serde_json::Value::String("Rejected".to_string()));
}
}
if let Some(comments) = &s.comments {
signer_map.insert("comments".to_string(), serde_json::Value::String(comments.clone()));
} else {
signer_map.insert("comments".to_string(), serde_json::Value::String("".to_string()));
}
serde_json::Value::Object(signer_map)
@ -195,14 +230,14 @@ impl ContractController {
map.insert("signers".to_string(), serde_json::Value::Array(signers));
// Add signed_signers count for templates
let signed_signers = contract.signers.iter().filter(|s| s.status == SignerStatus::Signed).count();
map.insert("signed_signers".to_string(), serde_json::Value::Number(serde_json::Number::from(signed_signers)));
// Add pending_signers count for templates
let pending_signers = contract.signers.iter().filter(|s| s.status == SignerStatus::Pending).count();
map.insert("pending_signers".to_string(), serde_json::Value::Number(serde_json::Number::from(pending_signers)));
// Add signed_signers count for templates
let signed_signers = contract.signers.iter().filter(|s| s.status == SignerStatus::Signed).count();
map.insert("signed_signers".to_string(), serde_json::Value::Number(serde_json::Number::from(signed_signers)));
// Add revisions
let revisions: Vec<serde_json::Value> = contract.revisions.iter()
.map(|r| {
@ -216,13 +251,63 @@ impl ContractController {
revision_map.insert("comments".to_string(), serde_json::Value::String(comments.clone()));
// Add notes field using comments since ContractRevision doesn't have a notes field
revision_map.insert("notes".to_string(), serde_json::Value::String(comments.clone()));
} else {
revision_map.insert("comments".to_string(), serde_json::Value::String("".to_string()));
revision_map.insert("notes".to_string(), serde_json::Value::String("".to_string()));
}
serde_json::Value::Object(revision_map)
})
.collect();
map.insert("revisions".to_string(), serde_json::Value::Array(revisions));
map.insert("revisions".to_string(), serde_json::Value::Array(revisions.clone()));
// Add current_version
map.insert("current_version".to_string(), serde_json::Value::Number(serde_json::Number::from(contract.current_version)));
// Add latest_revision as an object
if !contract.revisions.is_empty() {
// Find the latest revision based on version number
if let Some(latest) = contract.revisions.iter().max_by_key(|r| r.version) {
let mut latest_revision_map = serde_json::Map::new();
latest_revision_map.insert("version".to_string(), serde_json::Value::Number(serde_json::Number::from(latest.version)));
latest_revision_map.insert("content".to_string(), serde_json::Value::String(latest.content.clone()));
latest_revision_map.insert("created_at".to_string(), serde_json::Value::String(latest.created_at.format("%Y-%m-%d").to_string()));
latest_revision_map.insert("created_by".to_string(), serde_json::Value::String(latest.created_by.clone()));
if let Some(comments) = &latest.comments {
latest_revision_map.insert("comments".to_string(), serde_json::Value::String(comments.clone()));
latest_revision_map.insert("notes".to_string(), serde_json::Value::String(comments.clone()));
} else {
latest_revision_map.insert("comments".to_string(), serde_json::Value::String("".to_string()));
latest_revision_map.insert("notes".to_string(), serde_json::Value::String("".to_string()));
}
map.insert("latest_revision".to_string(), serde_json::Value::Object(latest_revision_map));
} else {
// Create an empty latest_revision object to avoid template errors
let mut empty_revision = serde_json::Map::new();
empty_revision.insert("version".to_string(), serde_json::Value::Number(serde_json::Number::from(0)));
empty_revision.insert("content".to_string(), serde_json::Value::String("No content available".to_string()));
empty_revision.insert("created_at".to_string(), serde_json::Value::String("N/A".to_string()));
empty_revision.insert("created_by".to_string(), serde_json::Value::String("N/A".to_string()));
empty_revision.insert("comments".to_string(), serde_json::Value::String("".to_string()));
empty_revision.insert("notes".to_string(), serde_json::Value::String("".to_string()));
map.insert("latest_revision".to_string(), serde_json::Value::Object(empty_revision));
}
} else {
// Create an empty latest_revision object to avoid template errors
let mut empty_revision = serde_json::Map::new();
empty_revision.insert("version".to_string(), serde_json::Value::Number(serde_json::Number::from(0)));
empty_revision.insert("content".to_string(), serde_json::Value::String("No content available".to_string()));
empty_revision.insert("created_at".to_string(), serde_json::Value::String("N/A".to_string()));
empty_revision.insert("created_by".to_string(), serde_json::Value::String("N/A".to_string()));
empty_revision.insert("comments".to_string(), serde_json::Value::String("".to_string()));
empty_revision.insert("notes".to_string(), serde_json::Value::String("".to_string()));
map.insert("latest_revision".to_string(), serde_json::Value::Object(empty_revision));
}
// Add effective and expiration dates if present
if let Some(effective_date) = &contract.effective_date {
@ -238,95 +323,269 @@ impl ContractController {
// Generate mock contracts for testing
fn get_mock_contracts() -> Vec<Contract> {
let now = Utc::now();
let mut contracts = Vec::new();
// Contract 1 - Draft
let mut contract1 = Contract::new(
"Employment Agreement - Marketing Manager".to_string(),
"Standard employment contract for the Marketing Manager position".to_string(),
ContractType::Employment,
"John Doe".to_string(),
Some("Acme Corp".to_string())
);
// Mock contract 1 - Signed Service Agreement
let mut contract1 = Contract {
id: "contract-001".to_string(),
title: "Digital Hub Service Agreement".to_string(),
description: "Service agreement for cloud hosting and digital infrastructure services provided by the Zanzibar Digital Hub.".to_string(),
status: ContractStatus::Signed,
contract_type: ContractType::Service,
created_by: "Wei Chen".to_string(),
created_at: Utc::now() - Duration::days(30),
updated_at: Utc::now() - Duration::days(5),
organization_id: Some("Zanzibar Digital Hub".to_string()),
effective_date: Some(Utc::now() - Duration::days(5)),
expiration_date: Some(Utc::now() + Duration::days(365)),
signers: Vec::new(),
revisions: Vec::new(),
current_version: 2,
};
contract1.effective_date = Some(now + Duration::days(30));
contract1.expiration_date = Some(now + Duration::days(395)); // ~1 year
// Add signers to contract 1
contract1.signers.push(ContractSigner {
id: "signer-001".to_string(),
name: "Wei Chen".to_string(),
email: "wei.chen@example.com".to_string(),
status: SignerStatus::Signed,
signed_at: Some(Utc::now() - Duration::days(5)),
comments: Some("Approved as per our discussion.".to_string()),
});
contract1.add_revision(
"<p>This is a draft employment contract for the Marketing Manager position.</p>".to_string(),
"John Doe".to_string(),
Some("Initial draft".to_string())
);
contract1.signers.push(ContractSigner {
id: "signer-002".to_string(),
name: "Nala Okafor".to_string(),
email: "nala.okafor@example.com".to_string(),
status: SignerStatus::Signed,
signed_at: Some(Utc::now() - Duration::days(6)),
comments: Some("Terms look good. Happy to proceed.".to_string()),
});
// Contract 2 - Pending Signatures
let mut contract2 = Contract::new(
"Non-Disclosure Agreement - Vendor XYZ".to_string(),
"NDA for our partnership with Vendor XYZ".to_string(),
ContractType::NDA,
"John Doe".to_string(),
Some("Acme Corp".to_string())
);
// Add revisions to contract 1
contract1.revisions.push(ContractRevision {
version: 1,
content: "<h1>Digital Hub Service Agreement</h1><p>This Service Agreement (the \"Agreement\") is entered into between Zanzibar Digital Hub (\"Provider\") and the undersigned client (\"Client\").</p><h2>1. Services</h2><p>Provider agrees to provide Client with cloud hosting and digital infrastructure services as specified in Appendix A.</p><h2>2. Term</h2><p>This Agreement shall commence on the Effective Date and continue for a period of one (1) year unless terminated earlier in accordance with the terms herein.</p><h2>3. Fees</h2><p>Client agrees to pay Provider the fees set forth in Appendix B. All fees are due within thirty (30) days of invoice date.</p><h2>4. Confidentiality</h2><p>Each party agrees to maintain the confidentiality of any proprietary information received from the other party during the term of this Agreement.</p>".to_string(),
created_at: Utc::now() - Duration::days(35),
created_by: "Wei Chen".to_string(),
comments: Some("Initial draft of the service agreement.".to_string()),
});
contract2.effective_date = Some(now);
contract2.expiration_date = Some(now + Duration::days(730)); // 2 years
contract1.revisions.push(ContractRevision {
version: 2,
content: "<h1>Digital Hub Service Agreement</h1><p>This Service Agreement (the \"Agreement\") is entered into between Zanzibar Digital Hub (\"Provider\") and the undersigned client (\"Client\").</p><h2>1. Services</h2><p>Provider agrees to provide Client with cloud hosting and digital infrastructure services as specified in Appendix A.</p><h2>2. Term</h2><p>This Agreement shall commence on the Effective Date and continue for a period of one (1) year unless terminated earlier in accordance with the terms herein.</p><h2>3. Fees</h2><p>Client agrees to pay Provider the fees set forth in Appendix B. All fees are due within thirty (30) days of invoice date.</p><h2>4. Confidentiality</h2><p>Each party agrees to maintain the confidentiality of any proprietary information received from the other party during the term of this Agreement.</p><h2>5. Data Protection</h2><p>Provider shall implement appropriate technical and organizational measures to ensure a level of security appropriate to the risk, including encryption of personal data, and shall comply with all applicable data protection laws.</p>".to_string(),
created_at: Utc::now() - Duration::days(30),
created_by: "Wei Chen".to_string(),
comments: Some("Added data protection clause as requested by legal.".to_string()),
});
contract2.add_revision(
"<p>This is the first version of the NDA with Vendor XYZ.</p>".to_string(),
"John Doe".to_string(),
Some("Initial draft".to_string())
);
// Mock contract 2 - Pending Signatures
let mut contract2 = Contract {
id: "contract-002".to_string(),
title: "Software Development Agreement".to_string(),
description: "Agreement for custom software development services for the Zanzibar Digital Marketplace platform.".to_string(),
status: ContractStatus::PendingSignatures,
contract_type: ContractType::SLA,
created_by: "Dr. Raj Patel".to_string(),
created_at: Utc::now() - Duration::days(10),
updated_at: Utc::now() - Duration::days(2),
organization_id: Some("Global Tech Solutions".to_string()),
effective_date: None,
expiration_date: None,
signers: Vec::new(),
revisions: Vec::new(),
current_version: 1,
};
contract2.add_revision(
"<p>This is the revised version of the NDA with Vendor XYZ.</p>".to_string(),
"John Doe".to_string(),
Some("Added confidentiality period".to_string())
);
// Add signers to contract 2
contract2.signers.push(ContractSigner {
id: "signer-003".to_string(),
name: "Dr. Raj Patel".to_string(),
email: "raj.patel@example.com".to_string(),
status: SignerStatus::Signed,
signed_at: Some(Utc::now() - Duration::days(2)),
comments: None,
});
contract2.add_signer("Jane Smith".to_string(), "jane@example.com".to_string());
contract2.add_signer("Bob Johnson".to_string(), "bob@vendorxyz.com".to_string());
contract2.signers.push(ContractSigner {
id: "signer-004".to_string(),
name: "Maya Rodriguez".to_string(),
email: "maya.rodriguez@example.com".to_string(),
status: SignerStatus::Pending,
signed_at: None,
comments: None,
});
// Mark Jane as signed
if let Some(signer) = contract2.signers.iter_mut().next() {
signer.sign(None);
}
contract2.signers.push(ContractSigner {
id: "signer-005".to_string(),
name: "Jamal Washington".to_string(),
email: "jamal.washington@example.com".to_string(),
status: SignerStatus::Pending,
signed_at: None,
comments: None,
});
// Send for signatures
let _ = contract2.send_for_signatures();
// Add revisions to contract 2
contract2.revisions.push(ContractRevision {
version: 1,
content: "<h1>Software Development Agreement</h1><p>This Software Development Agreement (the \"Agreement\") is entered into between Global Tech Solutions (\"Developer\") and Zanzibar Digital Hub (\"Client\").</p><h2>1. Scope of Work</h2><p>Developer agrees to design, develop, and implement a digital marketplace platform as specified in the attached Statement of Work.</p><h2>2. Timeline</h2><p>Developer shall complete the development according to the timeline set forth in Appendix A.</p><h2>3. Compensation</h2><p>Client agrees to pay Developer the fees set forth in Appendix B according to the payment schedule therein.</p><h2>4. Intellectual Property</h2><p>Upon full payment, Client shall own all rights, title, and interest in the developed software.</p>".to_string(),
created_at: Utc::now() - Duration::days(10),
created_by: "Dr. Raj Patel".to_string(),
comments: Some("Initial draft of the development agreement.".to_string()),
});
// Contract 3 - Signed
let mut contract3 = Contract::new(
"Service Agreement - Website Maintenance".to_string(),
"Agreement for ongoing website maintenance services".to_string(),
ContractType::Service,
"John Doe".to_string(),
Some("Acme Corp".to_string())
);
// Mock contract 3 - Draft
let mut contract3 = Contract {
id: "contract-003".to_string(),
title: "Digital Asset Tokenization Agreement".to_string(),
description: "Framework agreement for tokenizing real estate assets on the Zanzibar blockchain network.".to_string(),
status: ContractStatus::Draft,
contract_type: ContractType::Partnership,
created_by: "Nala Okafor".to_string(),
created_at: Utc::now() - Duration::days(3),
updated_at: Utc::now() - Duration::days(1),
organization_id: Some("Zanzibar Property Consortium".to_string()),
effective_date: None,
expiration_date: None,
signers: Vec::new(),
revisions: Vec::new(),
current_version: 1,
};
contract3.effective_date = Some(now - Duration::days(7));
contract3.expiration_date = Some(now + Duration::days(358)); // ~1 year from effective date
// Add potential signers to contract 3 (still in draft)
contract3.signers.push(ContractSigner {
id: "signer-006".to_string(),
name: "Nala Okafor".to_string(),
email: "nala.okafor@example.com".to_string(),
status: SignerStatus::Pending,
signed_at: None,
comments: None,
});
contract3.add_revision(
"<p>This is a service agreement for website maintenance.</p>".to_string(),
"John Doe".to_string(),
Some("Initial version".to_string())
);
contract3.signers.push(ContractSigner {
id: "signer-007".to_string(),
name: "Ibrahim Al-Farsi".to_string(),
email: "ibrahim.alfarsi@example.com".to_string(),
status: SignerStatus::Pending,
signed_at: None,
comments: None,
});
contract3.add_signer("Jane Smith".to_string(), "jane@example.com".to_string());
contract3.add_signer("Alice Brown".to_string(), "alice@webmaintenance.com".to_string());
// Add revisions to contract 3
contract3.revisions.push(ContractRevision {
version: 1,
content: "<h1>Digital Asset Tokenization Agreement</h1><p>This Digital Asset Tokenization Agreement (the \"Agreement\") is entered into between Zanzibar Property Consortium (\"Tokenizer\") and the property owners listed in Appendix A (\"Owners\").</p><h2>1. Purpose</h2><p>The purpose of this Agreement is to establish the terms and conditions for tokenizing real estate assets on the Zanzibar blockchain network.</p><h2>2. Tokenization Process</h2><p>Tokenizer shall create digital tokens representing ownership interests in the properties listed in Appendix A according to the specifications in Appendix B.</p><h2>3. Revenue Sharing</h2><p>Revenue generated from the tokenized properties shall be distributed according to the formula set forth in Appendix C.</p><h2>4. Governance</h2><p>Decisions regarding the management of tokenized properties shall be made according to the governance framework outlined in Appendix D.</p>".to_string(),
created_at: Utc::now() - Duration::days(3),
created_by: "Nala Okafor".to_string(),
comments: Some("Initial draft of the tokenization agreement.".to_string()),
});
// Mark both signers as signed
for signer in contract3.signers.iter_mut() {
signer.sign(None);
}
// Mock contract 4 - Rejected
let mut contract4 = Contract {
id: "contract-004".to_string(),
title: "Data Sharing Agreement".to_string(),
description: "Agreement governing the sharing of anonymized data between Zanzibar Digital Hub and research institutions.".to_string(),
status: ContractStatus::Draft,
contract_type: ContractType::NDA,
created_by: "Wei Chen".to_string(),
created_at: Utc::now() - Duration::days(15),
updated_at: Utc::now() - Duration::days(8),
organization_id: Some("Zanzibar Digital Hub".to_string()),
effective_date: None,
expiration_date: None,
signers: Vec::new(),
revisions: Vec::new(),
current_version: 1,
};
// Mark as signed
contract3.status = ContractStatus::Signed;
// Add signers to contract 4 with a rejection
contract4.signers.push(ContractSigner {
id: "signer-008".to_string(),
name: "Wei Chen".to_string(),
email: "wei.chen@example.com".to_string(),
status: SignerStatus::Signed,
signed_at: Some(Utc::now() - Duration::days(10)),
comments: None,
});
contract4.signers.push(ContractSigner {
id: "signer-009".to_string(),
name: "Dr. Amina Diallo".to_string(),
email: "amina.diallo@example.com".to_string(),
status: SignerStatus::Rejected,
signed_at: Some(Utc::now() - Duration::days(8)),
comments: Some("Cannot agree to these terms due to privacy concerns. Please revise section 3.2 regarding data retention.".to_string()),
});
// Add revisions to contract 4
contract4.revisions.push(ContractRevision {
version: 1,
content: "<h1>Data Sharing Agreement</h1><p>This Data Sharing Agreement (the \"Agreement\") is entered into between Zanzibar Digital Hub (\"Provider\") and the research institutions listed in Appendix A (\"Recipients\").</p><h2>1. Purpose</h2><p>The purpose of this Agreement is to establish the terms and conditions for sharing anonymized data for research purposes.</p><h2>2. Data Description</h2><p>Provider shall share the data described in Appendix B, which shall be anonymized according to the protocol in Appendix C.</p><h2>3. Data Use</h2><p>Recipients may use the shared data solely for the research purposes described in Appendix D.</p><h2>3.1 Publication</h2><p>Recipients may publish research findings based on the shared data, provided that they acknowledge Provider as the data source.</p><h2>3.2 Data Retention</h2><p>Recipients shall retain the shared data for a period of five (5) years, after which they shall securely delete all copies.</p>".to_string(),
created_at: Utc::now() - Duration::days(15),
created_by: "Wei Chen".to_string(),
comments: Some("Initial draft of the data sharing agreement.".to_string()),
});
// Mock contract 5 - Active
let mut contract5 = Contract {
id: "contract-005".to_string(),
title: "Digital Identity Verification Service Agreement".to_string(),
description: "Agreement for providing digital identity verification services to businesses operating in the Zanzibar Autonomous Zone.".to_string(),
status: ContractStatus::Active,
contract_type: ContractType::Service,
created_by: "Maya Rodriguez".to_string(),
created_at: Utc::now() - Duration::days(60),
updated_at: Utc::now() - Duration::days(45),
organization_id: Some("Zanzibar Digital Hub".to_string()),
effective_date: Some(Utc::now() - Duration::days(45)),
expiration_date: Some(Utc::now() + Duration::days(305)),
signers: Vec::new(),
revisions: Vec::new(),
current_version: 2,
};
// Add signers to contract 5
contract5.signers.push(ContractSigner {
id: "signer-010".to_string(),
name: "Maya Rodriguez".to_string(),
email: "maya.rodriguez@example.com".to_string(),
status: SignerStatus::Signed,
signed_at: Some(Utc::now() - Duration::days(47)),
comments: None,
});
contract5.signers.push(ContractSigner {
id: "signer-011".to_string(),
name: "Li Wei".to_string(),
email: "li.wei@example.com".to_string(),
status: SignerStatus::Signed,
signed_at: Some(Utc::now() - Duration::days(45)),
comments: Some("Approved after legal review.".to_string()),
});
// Add revisions to contract 5
contract5.revisions.push(ContractRevision {
version: 1,
content: "<h1>Digital Identity Verification Service Agreement</h1><p>This Service Agreement (the \"Agreement\") is entered into between Zanzibar Digital Hub (\"Provider\") and the businesses listed in Appendix A (\"Clients\").</p><h2>1. Services</h2><p>Provider agrees to provide Clients with digital identity verification services as specified in Appendix B.</p><h2>2. Term</h2><p>This Agreement shall commence on the Effective Date and continue for a period of one (1) year unless terminated earlier in accordance with the terms herein.</p><h2>3. Fees</h2><p>Clients agree to pay Provider the fees set forth in Appendix C. All fees are due within thirty (30) days of invoice date.</p><h2>4. Service Level Agreement</h2><p>Provider shall maintain a service uptime of at least 99.9% as measured on a monthly basis.</p>".to_string(),
created_at: Utc::now() - Duration::days(60),
created_by: "Maya Rodriguez".to_string(),
comments: Some("Initial draft of the identity verification service agreement.".to_string()),
});
contract5.revisions.push(ContractRevision {
version: 2,
content: "<h1>Digital Identity Verification Service Agreement</h1><p>This Service Agreement (the \"Agreement\") is entered into between Zanzibar Digital Hub (\"Provider\") and the businesses listed in Appendix A (\"Clients\").</p><h2>1. Services</h2><p>Provider agrees to provide Clients with digital identity verification services as specified in Appendix B.</p><h2>2. Term</h2><p>This Agreement shall commence on the Effective Date and continue for a period of one (1) year unless terminated earlier in accordance with the terms herein.</p><h2>3. Fees</h2><p>Clients agree to pay Provider the fees set forth in Appendix C. All fees are due within thirty (30) days of invoice date.</p><h2>4. Service Level Agreement</h2><p>Provider shall maintain a service uptime of at least 99.9% as measured on a monthly basis.</p><h2>5. Compliance</h2><p>Provider shall comply with all applicable laws and regulations regarding identity verification and data protection, including but not limited to the Zanzibar Digital Economy Act.</p>".to_string(),
created_at: Utc::now() - Duration::days(50),
created_by: "Maya Rodriguez".to_string(),
comments: Some("Added compliance clause as requested by legal.".to_string()),
});
// Add all contracts to the vector
contracts.push(contract1);
contracts.push(contract2);
contracts.push(contract3);
contracts.push(contract4);
contracts.push(contract5);
contracts
}

View File

@ -187,226 +187,421 @@ impl FlowController {
// Create a few mock flows
let mut flow1 = Flow {
id: "flow-1".to_string(),
name: "Deploy Website".to_string(),
description: "Deploy a new website to production".to_string(),
flow_type: FlowType::ServiceActivation,
name: "ZAZ Business Entity Registration".to_string(),
description: "Register a new business entity within the Zanzibar Autonomous Zone legal framework".to_string(),
flow_type: FlowType::CompanyRegistration,
status: FlowStatus::InProgress,
owner_id: "user-1".to_string(),
owner_name: "John Doe".to_string(),
owner_name: "Ibrahim Faraji".to_string(),
steps: vec![
FlowStep {
id: "step-1-1".to_string(),
name: "Build".to_string(),
description: "Build the website".to_string(),
name: "Document Submission".to_string(),
description: "Submit required business registration documents including business plan, ownership structure, and KYC information".to_string(),
order: 1,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(1)),
completed_at: Some(Utc::now() - Duration::hours(23)),
started_at: Some(Utc::now() - Duration::days(5)),
completed_at: Some(Utc::now() - Duration::days(4)),
logs: vec![
FlowLog {
id: "log-1-1-1".to_string(),
message: "Build started".to_string(),
timestamp: Utc::now() - Duration::days(1),
message: "Initial document package submitted".to_string(),
timestamp: Utc::now() - Duration::days(5),
},
FlowLog {
id: "log-1-1-2".to_string(),
message: "Build completed successfully".to_string(),
timestamp: Utc::now() - Duration::hours(23),
message: "Additional ownership verification documents requested".to_string(),
timestamp: Utc::now() - Duration::days(4) - Duration::hours(12),
},
FlowLog {
id: "log-1-1-3".to_string(),
message: "Additional documents submitted and verified".to_string(),
timestamp: Utc::now() - Duration::days(4),
},
],
},
FlowStep {
id: "step-1-2".to_string(),
name: "Test".to_string(),
description: "Run tests on the website".to_string(),
name: "Regulatory Review".to_string(),
description: "ZAZ Business Registry review of submitted documents and compliance with regulatory requirements".to_string(),
order: 2,
status: StepStatus::InProgress,
started_at: Some(Utc::now() - Duration::hours(22)),
started_at: Some(Utc::now() - Duration::days(3)),
completed_at: None,
logs: vec![
FlowLog {
id: "log-1-2-1".to_string(),
message: "Tests started".to_string(),
timestamp: Utc::now() - Duration::hours(22),
message: "Regulatory review initiated by ZAZ Business Registry".to_string(),
timestamp: Utc::now() - Duration::days(3),
},
FlowLog {
id: "log-1-2-2".to_string(),
message: "Preliminary compliance assessment completed".to_string(),
timestamp: Utc::now() - Duration::days(2),
},
FlowLog {
id: "log-1-2-3".to_string(),
message: "Awaiting final approval from regulatory committee".to_string(),
timestamp: Utc::now() - Duration::days(1),
},
],
},
FlowStep {
id: "step-1-3".to_string(),
name: "Deploy".to_string(),
description: "Deploy the website to production".to_string(),
name: "Digital Identity Creation".to_string(),
description: "Creation of the entity's digital identity and blockchain credentials within the ZAZ ecosystem".to_string(),
order: 3,
status: StepStatus::Pending,
started_at: None,
completed_at: None,
logs: vec![],
},
FlowStep {
id: "step-1-4".to_string(),
name: "License and Certificate Issuance".to_string(),
description: "Issuance of business licenses, certificates, and digital credentials".to_string(),
order: 4,
status: StepStatus::Pending,
started_at: None,
completed_at: None,
logs: vec![],
},
],
created_at: Utc::now() - Duration::days(1),
updated_at: Utc::now() - Duration::hours(22),
created_at: Utc::now() - Duration::days(5),
updated_at: Utc::now() - Duration::days(1),
completed_at: None,
progress_percentage: 33,
progress_percentage: 40,
current_step: None,
};
// Update the current step
flow1.current_step = flow1.steps.iter().find(|s| s.status == StepStatus::InProgress).cloned();
let flow2 = Flow {
let mut flow2 = Flow {
id: "flow-2".to_string(),
name: "Database Migration".to_string(),
description: "Migrate database to new schema".to_string(),
flow_type: FlowType::CompanyRegistration,
name: "Digital Asset Tokenization Approval".to_string(),
description: "Process for approving the tokenization of a real estate asset within the ZAZ regulatory framework".to_string(),
flow_type: FlowType::AssetTokenization,
status: FlowStatus::Completed,
owner_id: "user-2".to_string(),
owner_name: "Jane Smith".to_string(),
owner_name: "Amina Salim".to_string(),
steps: vec![
FlowStep {
id: "step-2-1".to_string(),
name: "Backup".to_string(),
description: "Backup the database".to_string(),
name: "Asset Verification".to_string(),
description: "Verification of the underlying asset ownership and valuation".to_string(),
order: 1,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(3)),
completed_at: Some(Utc::now() - Duration::days(3) + Duration::hours(2)),
started_at: Some(Utc::now() - Duration::days(30)),
completed_at: Some(Utc::now() - Duration::days(25)),
logs: vec![
FlowLog {
id: "log-2-1-1".to_string(),
message: "Backup started".to_string(),
timestamp: Utc::now() - Duration::days(3),
message: "Asset documentation submitted for verification".to_string(),
timestamp: Utc::now() - Duration::days(30),
},
FlowLog {
id: "log-2-1-2".to_string(),
message: "Backup completed successfully".to_string(),
timestamp: Utc::now() - Duration::days(3) + Duration::hours(2),
message: "Independent valuation completed by ZAZ Property Registry".to_string(),
timestamp: Utc::now() - Duration::days(27),
},
FlowLog {
id: "log-2-1-3".to_string(),
message: "Asset ownership and valuation verified".to_string(),
timestamp: Utc::now() - Duration::days(25),
},
],
},
FlowStep {
id: "step-2-2".to_string(),
name: "Migrate".to_string(),
description: "Run migration scripts".to_string(),
name: "Tokenization Structure Review".to_string(),
description: "Review of the proposed token structure, distribution model, and compliance with ZAZ tokenization standards".to_string(),
order: 2,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(3) + Duration::hours(3)),
completed_at: Some(Utc::now() - Duration::days(3) + Duration::hours(5)),
started_at: Some(Utc::now() - Duration::days(24)),
completed_at: Some(Utc::now() - Duration::days(20)),
logs: vec![
FlowLog {
id: "log-2-2-1".to_string(),
message: "Migration started".to_string(),
timestamp: Utc::now() - Duration::days(3) + Duration::hours(3),
message: "Tokenization proposal submitted for review".to_string(),
timestamp: Utc::now() - Duration::days(24),
},
FlowLog {
id: "log-2-2-2".to_string(),
message: "Migration completed successfully".to_string(),
timestamp: Utc::now() - Duration::days(3) + Duration::hours(5),
message: "Technical review completed by ZAZ Digital Assets Committee".to_string(),
timestamp: Utc::now() - Duration::days(22),
},
FlowLog {
id: "log-2-2-3".to_string(),
message: "Tokenization structure approved with minor modifications".to_string(),
timestamp: Utc::now() - Duration::days(20),
},
],
},
FlowStep {
id: "step-2-3".to_string(),
name: "Verify".to_string(),
description: "Verify the migration".to_string(),
name: "Smart Contract Deployment".to_string(),
description: "Deployment and verification of the asset tokenization smart contracts".to_string(),
order: 3,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(3) + Duration::hours(6)),
completed_at: Some(Utc::now() - Duration::days(3) + Duration::hours(7)),
started_at: Some(Utc::now() - Duration::days(19)),
completed_at: Some(Utc::now() - Duration::days(15)),
logs: vec![
FlowLog {
id: "log-2-3-1".to_string(),
message: "Verification started".to_string(),
timestamp: Utc::now() - Duration::days(3) + Duration::hours(6),
message: "Smart contract code submitted for audit".to_string(),
timestamp: Utc::now() - Duration::days(19),
},
FlowLog {
id: "log-2-3-2".to_string(),
message: "Verification completed successfully".to_string(),
timestamp: Utc::now() - Duration::days(3) + Duration::hours(7),
message: "Security audit completed with no critical issues".to_string(),
timestamp: Utc::now() - Duration::days(17),
},
FlowLog {
id: "log-2-3-3".to_string(),
message: "Smart contracts deployed to ZAZ-approved blockchain".to_string(),
timestamp: Utc::now() - Duration::days(15),
},
],
},
FlowStep {
id: "step-2-4".to_string(),
name: "Final Approval and Listing".to_string(),
description: "Final regulatory approval and listing on the ZAZ Digital Asset Exchange".to_string(),
order: 4,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(14)),
completed_at: Some(Utc::now() - Duration::days(10)),
logs: vec![
FlowLog {
id: "log-2-4-1".to_string(),
message: "Final documentation package submitted for approval".to_string(),
timestamp: Utc::now() - Duration::days(14),
},
FlowLog {
id: "log-2-4-2".to_string(),
message: "Regulatory approval granted by ZAZ Financial Authority".to_string(),
timestamp: Utc::now() - Duration::days(12),
},
FlowLog {
id: "log-2-4-3".to_string(),
message: "Asset tokens listed on ZAZ Digital Asset Exchange".to_string(),
timestamp: Utc::now() - Duration::days(10),
},
],
},
],
created_at: Utc::now() - Duration::days(3),
updated_at: Utc::now() - Duration::days(3) + Duration::hours(7),
completed_at: Some(Utc::now() - Duration::days(3) + Duration::hours(7)),
created_at: Utc::now() - Duration::days(30),
updated_at: Utc::now() - Duration::days(10),
completed_at: Some(Utc::now() - Duration::days(10)),
progress_percentage: 100,
current_step: None,
};
flow2.current_step = flow2.steps.last().cloned();
let mut flow3 = Flow {
id: "flow-3".to_string(),
name: "Server Maintenance".to_string(),
description: "Perform server maintenance".to_string(),
flow_type: FlowType::PaymentProcessing,
name: "Sustainable Tourism Certification".to_string(),
description: "Application process for ZAZ Sustainable Tourism Certification for eco-tourism businesses".to_string(),
flow_type: FlowType::Certification,
status: FlowStatus::Stuck,
owner_id: "user-1".to_string(),
owner_name: "John Doe".to_string(),
owner_id: "user-3".to_string(),
owner_name: "Hassan Mwinyi".to_string(),
steps: vec![
FlowStep {
id: "step-3-1".to_string(),
name: "Backup".to_string(),
description: "Backup the server".to_string(),
name: "Initial Application".to_string(),
description: "Submission of initial application and supporting documentation".to_string(),
order: 1,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(2)),
completed_at: Some(Utc::now() - Duration::days(2) + Duration::hours(1)),
started_at: Some(Utc::now() - Duration::days(15)),
completed_at: Some(Utc::now() - Duration::days(12)),
logs: vec![
FlowLog {
id: "log-3-1-1".to_string(),
message: "Backup started".to_string(),
timestamp: Utc::now() - Duration::days(2),
message: "Application submitted for Coral Reef Eco Tours".to_string(),
timestamp: Utc::now() - Duration::days(15),
},
FlowLog {
id: "log-3-1-2".to_string(),
message: "Backup completed successfully".to_string(),
timestamp: Utc::now() - Duration::days(2) + Duration::hours(1),
message: "Application fee payment confirmed".to_string(),
timestamp: Utc::now() - Duration::days(14),
},
FlowLog {
id: "log-3-1-3".to_string(),
message: "Initial documentation review completed".to_string(),
timestamp: Utc::now() - Duration::days(12),
},
],
},
FlowStep {
id: "step-3-2".to_string(),
name: "Update".to_string(),
description: "Update the server".to_string(),
name: "Environmental Impact Assessment".to_string(),
description: "Assessment of the business's environmental impact and sustainability practices".to_string(),
order: 2,
status: StepStatus::Stuck,
started_at: Some(Utc::now() - Duration::days(2) + Duration::hours(2)),
started_at: Some(Utc::now() - Duration::days(11)),
completed_at: None,
logs: vec![
FlowLog {
id: "log-3-2-1".to_string(),
message: "Update started".to_string(),
timestamp: Utc::now() - Duration::days(2) + Duration::hours(2),
message: "Environmental assessment initiated".to_string(),
timestamp: Utc::now() - Duration::days(11),
},
FlowLog {
id: "log-3-2-2".to_string(),
message: "Update failed: Disk space issue".to_string(),
timestamp: Utc::now() - Duration::days(2) + Duration::hours(3),
message: "Site visit scheduled with environmental officer".to_string(),
timestamp: Utc::now() - Duration::days(9),
},
FlowLog {
id: "log-3-2-3".to_string(),
message: "STUCK: Missing required marine conservation plan documentation".to_string(),
timestamp: Utc::now() - Duration::days(7),
},
],
},
FlowStep {
id: "step-3-3".to_string(),
name: "Restart".to_string(),
description: "Restart the server".to_string(),
name: "Community Engagement Verification".to_string(),
description: "Verification of community engagement and benefit-sharing mechanisms".to_string(),
order: 3,
status: StepStatus::Pending,
started_at: None,
completed_at: None,
logs: vec![],
},
FlowStep {
id: "step-3-4".to_string(),
name: "Certification Issuance".to_string(),
description: "Final review and issuance of ZAZ Sustainable Tourism Certification".to_string(),
order: 4,
status: StepStatus::Pending,
started_at: None,
completed_at: None,
logs: vec![],
},
],
created_at: Utc::now() - Duration::days(2),
updated_at: Utc::now() - Duration::days(2) + Duration::hours(3),
created_at: Utc::now() - Duration::days(15),
updated_at: Utc::now() - Duration::days(7),
completed_at: None,
progress_percentage: 33,
progress_percentage: 35,
current_step: None,
};
// Update the current step
flow3.current_step = flow3.steps.iter().find(|s| s.status == StepStatus::Stuck).cloned();
let mut flow4 = Flow {
id: "flow-4".to_string(),
name: "Digital Payment Provider License".to_string(),
description: "Application for a license to operate as a digital payment provider within the ZAZ financial system".to_string(),
flow_type: FlowType::LicenseApplication,
status: FlowStatus::InProgress,
owner_id: "user-4".to_string(),
owner_name: "Fatma Busaidy".to_string(),
steps: vec![
FlowStep {
id: "step-4-1".to_string(),
name: "Initial Application".to_string(),
description: "Submission of license application and company information".to_string(),
order: 1,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(20)),
completed_at: Some(Utc::now() - Duration::days(18)),
logs: vec![
FlowLog {
id: "log-4-1-1".to_string(),
message: "Application submitted for ZanziPay digital payment services".to_string(),
timestamp: Utc::now() - Duration::days(20),
},
FlowLog {
id: "log-4-1-2".to_string(),
message: "Application fee payment confirmed".to_string(),
timestamp: Utc::now() - Duration::days(19),
},
FlowLog {
id: "log-4-1-3".to_string(),
message: "Initial documentation review completed".to_string(),
timestamp: Utc::now() - Duration::days(18),
},
],
},
FlowStep {
id: "step-4-2".to_string(),
name: "Technical Infrastructure Review".to_string(),
description: "Review of the technical infrastructure, security measures, and compliance with ZAZ financial standards".to_string(),
order: 2,
status: StepStatus::Completed,
started_at: Some(Utc::now() - Duration::days(17)),
completed_at: Some(Utc::now() - Duration::days(10)),
logs: vec![
FlowLog {
id: "log-4-2-1".to_string(),
message: "Technical documentation submitted for review".to_string(),
timestamp: Utc::now() - Duration::days(17),
},
FlowLog {
id: "log-4-2-2".to_string(),
message: "Security audit initiated by ZAZ Financial Technology Office".to_string(),
timestamp: Utc::now() - Duration::days(15),
},
FlowLog {
id: "log-4-2-3".to_string(),
message: "Technical infrastructure approved with recommendations".to_string(),
timestamp: Utc::now() - Duration::days(10),
},
],
},
FlowStep {
id: "step-4-3".to_string(),
name: "AML/KYC Compliance Review".to_string(),
description: "Review of anti-money laundering and know-your-customer procedures".to_string(),
order: 3,
status: StepStatus::InProgress,
started_at: Some(Utc::now() - Duration::days(9)),
completed_at: None,
logs: vec![
FlowLog {
id: "log-4-3-1".to_string(),
message: "AML/KYC documentation submitted for review".to_string(),
timestamp: Utc::now() - Duration::days(9),
},
FlowLog {
id: "log-4-3-2".to_string(),
message: "Initial compliance assessment completed".to_string(),
timestamp: Utc::now() - Duration::days(5),
},
FlowLog {
id: "log-4-3-3".to_string(),
message: "Additional KYC procedure documentation requested".to_string(),
timestamp: Utc::now() - Duration::days(3),
},
],
},
FlowStep {
id: "step-4-4".to_string(),
name: "License Issuance".to_string(),
description: "Final review and issuance of Digital Payment Provider License".to_string(),
order: 4,
status: StepStatus::Pending,
started_at: None,
completed_at: None,
logs: vec![],
},
],
created_at: Utc::now() - Duration::days(20),
updated_at: Utc::now() - Duration::days(3),
completed_at: None,
progress_percentage: 65,
current_step: None,
};
flow4.current_step = flow4.steps.iter().find(|s| s.status == StepStatus::InProgress).cloned();
flows.push(flow1);
flows.push(flow2);
flows.push(flow3);
flows.push(flow4);
flows
}

View File

@ -228,9 +228,9 @@ impl GovernanceController {
Proposal {
id: "prop-001".to_string(),
creator_id: 1,
creator_name: "John Doe".to_string(),
title: "Implement Community Rewards Program".to_string(),
description: "This proposal aims to implement a community rewards program to incentivize active participation in the platform.".to_string(),
creator_name: "Ibrahim Faraji".to_string(),
title: "Establish Zanzibar Digital Trade Hub".to_string(),
description: "This proposal aims to create a dedicated digital trade hub within the Zanzibar Autonomous Zone to facilitate international e-commerce for local businesses. The hub will provide logistics support, digital marketing services, and regulatory compliance assistance to help Zanzibar businesses reach global markets.".to_string(),
status: ProposalStatus::Active,
created_at: now - Duration::days(5),
updated_at: now - Duration::days(5),
@ -240,9 +240,9 @@ impl GovernanceController {
Proposal {
id: "prop-002".to_string(),
creator_id: 2,
creator_name: "Jane Smith".to_string(),
title: "Platform UI Redesign".to_string(),
description: "A comprehensive redesign of the platform's user interface to improve usability and accessibility.".to_string(),
creator_name: "Amina Salim".to_string(),
title: "ZAZ Sustainable Tourism Framework".to_string(),
description: "A comprehensive framework for sustainable tourism development within the Zanzibar Autonomous Zone. This proposal outlines environmental standards, community benefit-sharing mechanisms, and digital infrastructure for eco-tourism businesses. It includes tokenization standards for tourism assets and a certification system for sustainable operators.".to_string(),
status: ProposalStatus::Approved,
created_at: now - Duration::days(15),
updated_at: now - Duration::days(2),
@ -252,9 +252,9 @@ impl GovernanceController {
Proposal {
id: "prop-003".to_string(),
creator_id: 3,
creator_name: "Bob Johnson".to_string(),
title: "Add Support for Mobile Notifications".to_string(),
description: "Implement push notifications for mobile users to improve engagement and user experience.".to_string(),
creator_name: "Hassan Mwinyi".to_string(),
title: "Spice Industry Modernization Initiative".to_string(),
description: "This proposal seeks to modernize Zanzibar's traditional spice industry through blockchain-based supply chain tracking, international quality certification, and digital marketplace integration. The initiative will help local spice farmers and processors access premium international markets while preserving traditional cultivation methods.".to_string(),
status: ProposalStatus::Draft,
created_at: now - Duration::days(1),
updated_at: now - Duration::days(1),
@ -264,9 +264,9 @@ impl GovernanceController {
Proposal {
id: "prop-004".to_string(),
creator_id: 1,
creator_name: "John Doe".to_string(),
title: "Integrate with Third-party Payment Providers".to_string(),
description: "Add support for additional payment providers to give users more options for transactions.".to_string(),
creator_name: "Ibrahim Faraji".to_string(),
title: "ZAZ Regulatory Framework for Digital Financial Services".to_string(),
description: "Establish a comprehensive regulatory framework for digital financial services within the Zanzibar Autonomous Zone. This includes licensing requirements for crypto exchanges, digital payment providers, and tokenized asset platforms operating within the zone, while ensuring compliance with international AML/KYC standards.".to_string(),
status: ProposalStatus::Rejected,
created_at: now - Duration::days(20),
updated_at: now - Duration::days(5),
@ -276,15 +276,39 @@ impl GovernanceController {
Proposal {
id: "prop-005".to_string(),
creator_id: 4,
creator_name: "Alice Williams".to_string(),
title: "Implement Two-Factor Authentication".to_string(),
description: "Enhance security by implementing two-factor authentication for all user accounts.".to_string(),
creator_name: "Fatma Busaidy".to_string(),
title: "Digital Arts Incubator and NFT Marketplace".to_string(),
description: "Create a dedicated digital arts incubator and NFT marketplace to support Zanzibar's creative economy. The initiative will provide technical training, equipment, and a curated marketplace for local artists to create and sell digital art that celebrates Zanzibar's rich cultural heritage while accessing global markets.".to_string(),
status: ProposalStatus::Active,
created_at: now - Duration::days(7),
updated_at: now - Duration::days(7),
voting_starts_at: Some(now - Duration::days(6)),
voting_ends_at: Some(now + Duration::days(1)),
},
Proposal {
id: "prop-006".to_string(),
creator_id: 5,
creator_name: "Omar Makame".to_string(),
title: "Zanzibar Renewable Energy Microgrid Network".to_string(),
description: "Develop a network of renewable energy microgrids across the Zanzibar Autonomous Zone using tokenized investment and community ownership models. This proposal outlines the technical specifications, governance structure, and token economics for deploying solar and tidal energy systems that will ensure energy independence for the zone.".to_string(),
status: ProposalStatus::Active,
created_at: now - Duration::days(10),
updated_at: now - Duration::days(9),
voting_starts_at: Some(now - Duration::days(8)),
voting_ends_at: Some(now + Duration::days(6)),
},
Proposal {
id: "prop-007".to_string(),
creator_id: 6,
creator_name: "Saida Juma".to_string(),
title: "ZAZ Educational Technology Initiative".to_string(),
description: "Establish a comprehensive educational technology program within the Zanzibar Autonomous Zone to develop local tech talent. This initiative includes coding academies, blockchain development courses, and digital entrepreneurship training, with a focus on preparing Zanzibar's youth for careers in the zone's growing digital economy.".to_string(),
status: ProposalStatus::Draft,
created_at: now - Duration::days(3),
updated_at: now - Duration::days(2),
voting_starts_at: None,
voting_ends_at: None,
},
]
}
@ -301,7 +325,7 @@ impl GovernanceController {
id: "vote-001".to_string(),
proposal_id: proposal_id.to_string(),
voter_id: 1,
voter_name: "John Doe".to_string(),
voter_name: "Robert Callingham".to_string(),
vote_type: VoteType::Yes,
comment: Some("I strongly support this initiative.".to_string()),
created_at: now - Duration::days(2),
@ -347,7 +371,7 @@ impl GovernanceController {
id: "vote-001".to_string(),
proposal_id: "prop-001".to_string(),
voter_id: user_id,
voter_name: "John Doe".to_string(),
voter_name: "Robert Callingham".to_string(),
vote_type: VoteType::Yes,
comment: Some("I strongly support this initiative.".to_string()),
created_at: Utc::now() - Duration::days(2),
@ -357,7 +381,7 @@ impl GovernanceController {
id: "vote-005".to_string(),
proposal_id: "prop-002".to_string(),
voter_id: user_id,
voter_name: "John Doe".to_string(),
voter_name: "Robert Callingham".to_string(),
vote_type: VoteType::No,
comment: Some("I don't think this is a priority right now.".to_string()),
created_at: Utc::now() - Duration::days(10),
@ -367,7 +391,7 @@ impl GovernanceController {
id: "vote-008".to_string(),
proposal_id: "prop-004".to_string(),
voter_id: user_id,
voter_name: "John Doe".to_string(),
voter_name: "Robert Callingham".to_string(),
vote_type: VoteType::Yes,
comment: None,
created_at: Utc::now() - Duration::days(18),
@ -377,7 +401,7 @@ impl GovernanceController {
id: "vote-010".to_string(),
proposal_id: "prop-005".to_string(),
voter_id: user_id,
voter_name: "John Doe".to_string(),
voter_name: "Robert Callingham".to_string(),
vote_type: VoteType::Yes,
comment: Some("Security is always a top priority.".to_string()),
created_at: Utc::now() - Duration::days(5),

View File

@ -8,6 +8,7 @@ pub enum ContractStatus {
Draft,
PendingSignatures,
Signed,
Active,
Expired,
Cancelled
}
@ -18,6 +19,7 @@ impl ContractStatus {
ContractStatus::Draft => "Draft",
ContractStatus::PendingSignatures => "Pending Signatures",
ContractStatus::Signed => "Signed",
ContractStatus::Active => "Active",
ContractStatus::Expired => "Expired",
ContractStatus::Cancelled => "Cancelled",
}
@ -31,6 +33,10 @@ pub enum ContractType {
Employment,
NDA,
SLA,
Partnership,
Distribution,
License,
Membership,
Other
}
@ -41,6 +47,10 @@ impl ContractType {
ContractType::Employment => "Employment Contract",
ContractType::NDA => "Non-Disclosure Agreement",
ContractType::SLA => "Service Level Agreement",
ContractType::Partnership => "Partnership Agreement",
ContractType::Distribution => "Distribution Agreement",
ContractType::License => "License Agreement",
ContractType::Membership => "Membership Agreement",
ContractType::Other => "Other",
}
}

View File

@ -37,6 +37,12 @@ pub enum FlowType {
ServiceActivation,
/// Payment processing flow
PaymentProcessing,
/// Asset tokenization flow
AssetTokenization,
/// Certification flow
Certification,
/// License application flow
LicenseApplication,
}
impl std::fmt::Display for FlowType {
@ -46,6 +52,9 @@ impl std::fmt::Display for FlowType {
FlowType::UserOnboarding => write!(f, "User Onboarding"),
FlowType::ServiceActivation => write!(f, "Service Activation"),
FlowType::PaymentProcessing => write!(f, "Payment Processing"),
FlowType::AssetTokenization => write!(f, "Asset Tokenization"),
FlowType::Certification => write!(f, "Certification"),
FlowType::LicenseApplication => write!(f, "License Application"),
}
}
}

View File

@ -145,8 +145,8 @@ mod tests {
#[test]
fn test_new_user() {
let user = User::new("John Doe".to_string(), "john@example.com".to_string());
assert_eq!(user.name, "John Doe");
let user = User::new("Robert Callingham".to_string(), "john@example.com".to_string());
assert_eq!(user.name, "Robert Callingham");
assert_eq!(user.email, "john@example.com");
assert!(!user.is_admin());
}
@ -161,13 +161,13 @@ mod tests {
#[test]
fn test_update_user() {
let mut user = User::new("John Doe".to_string(), "john@example.com".to_string());
user.update(Some("Jane Doe".to_string()), None);
assert_eq!(user.name, "Jane Doe");
let mut user = User::new("Robert Callingham".to_string(), "john@example.com".to_string());
user.update(Some("Mary Hewell".to_string()), None);
assert_eq!(user.name, "Mary Hewell");
assert_eq!(user.email, "john@example.com");
user.update(None, Some("jane@example.com".to_string()));
assert_eq!(user.name, "Jane Doe");
assert_eq!(user.name, "Mary Hewell");
assert_eq!(user.email, "jane@example.com");
}
}

View File

@ -1,6 +1,7 @@
use actix_web::{error, Error, HttpResponse};
use chrono::{DateTime, Utc};
use tera::{self, Context, Function, Tera, Value};
use std::error::Error as StdError;
// Export modules
pub mod redis_service;
@ -137,39 +138,58 @@ pub fn render_template(
Ok(HttpResponse::Ok().content_type("text/html").body(content))
},
Err(e) => {
// Log the error
// Log the error with more details
println!("DEBUG: Template rendering error for {}: {}", template_name, e);
println!("DEBUG: Error details: {:?}", e);
// Print the error cause chain for better debugging
let mut current_error: Option<&dyn StdError> = Some(&e);
let mut error_chain = Vec::new();
while let Some(error) = current_error {
error_chain.push(format!("{}", error));
current_error = error.source();
}
println!("DEBUG: Error chain: {:?}", error_chain);
// Log the error
log::error!("Template rendering error: {}", e);
// Create a simple error response instead of trying to render the error template
// Create a simple error response with more detailed information
let error_html = format!(
r#"<!DOCTYPE html>
<html>
<head>
<title>Template Error</title>
<style>
body {{ font-family: Arial, sans-serif; margin: 40px; }}
.error-container {{ border: 1px solid #dc3545; padding: 20px; border-radius: 5px; }}
h1 {{ color: #dc3545; }}
pre {{ background-color: #f8f9fa; padding: 15px; border-radius: 5px; overflow-x: auto; }}
body {{ font-family: Arial, sans-serif; margin: 40px; line-height: 1.6; }}
.error-container {{ border: 1px solid #f5c6cb; background-color: #f8d7da; padding: 20px; border-radius: 5px; }}
.error-title {{ color: #721c24; }}
.error-details {{ background-color: #f8f9fa; padding: 15px; border-radius: 5px; margin-top: 20px; }}
pre {{ background-color: #f1f1f1; padding: 10px; overflow: auto; }}
</style>
</head>
<body>
<div class="error-container">
<h1>Template Rendering Error</h1>
<p><strong>Template:</strong> {}</p>
<p><strong>Error:</strong></p>
<pre>{}</pre>
<p><a href="/">Return to Home</a></p>
<h1 class="error-title">Template Rendering Error</h1>
<p>There was an error rendering the template: <strong>{}</strong></p>
<div class="error-details">
<h3>Error Details:</h3>
<pre>{}</pre>
<h3>Error Chain:</h3>
<pre>{}</pre>
</div>
</div>
</body>
</html>"#,
template_name,
e
e,
error_chain.join("\n")
);
println!("DEBUG: Returning simple error page");
Ok(HttpResponse::InternalServerError()
.content_type("text/html")
.body(error_html))

View File

@ -24,302 +24,309 @@
</div>
</div>
<!-- Contract Overview -->
<div class="row mb-4">
<div class="col-md-8">
<div class="card h-100">
<div class="card-header">
<h5 class="mb-0">Contract Overview</h5>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-3 fw-bold">Status:</div>
<div class="col-md-9">
<span class="badge {% if contract.status == 'Signed' %}bg-success{% elif contract.status == 'PendingSignatures' %}bg-warning text-dark{% elif contract.status == 'Draft' %}bg-secondary{% elif contract.status == 'Expired' %}bg-danger{% else %}bg-dark{% endif %}">
{{ contract.status }}
</span>
</div>
</div>
<div class="row mb-3">
<div class="col-md-3 fw-bold">Type:</div>
<div class="col-md-9">{{ contract.contract_type }}</div>
</div>
<div class="row mb-3">
<div class="col-md-3 fw-bold">Created By:</div>
<div class="col-md-9">{{ contract.created_by }}</div>
</div>
<div class="row mb-3">
<div class="col-md-3 fw-bold">Created:</div>
<div class="col-md-9">{{ contract.created_at }}</div>
</div>
<div class="row mb-3">
<div class="col-md-3 fw-bold">Last Updated:</div>
<div class="col-md-9">{{ contract.updated_at }}</div>
</div>
{% if contract.effective_date %}
<div class="row mb-3">
<div class="col-md-3 fw-bold">Effective Date:</div>
<div class="col-md-9">{{ contract.effective_date }}</div>
</div>
{% endif %}
{% if contract.expiration_date %}
<div class="row mb-3">
<div class="col-md-3 fw-bold">Expiration Date:</div>
<div class="col-md-9">{{ contract.expiration_date }}</div>
</div>
{% endif %}
<div class="row mb-3">
<div class="col-md-3 fw-bold">Version:</div>
<div class="col-md-9">{{ contract.current_version }}</div>
</div>
<div class="row">
<div class="col-md-3 fw-bold">Description:</div>
<div class="col-md-9">{{ contract.description }}</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card h-100">
<div class="card-header">
<h5 class="mb-0">Actions</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
{% if contract.status == 'Draft' %}
<a href="/contracts/{{ contract.id }}/edit" class="btn btn-primary">
<i class="bi bi-pencil me-1"></i> Edit Contract
</a>
<a href="/contracts/{{ contract.id }}/send" class="btn btn-success">
<i class="bi bi-send me-1"></i> Send for Signatures
</a>
<button class="btn btn-danger">
<i class="bi bi-trash me-1"></i> Delete Contract
</button>
{% elif contract.status == 'PendingSignatures' %}
<button class="btn btn-success">
<i class="bi bi-pen me-1"></i> Sign Contract
</button>
<button class="btn btn-warning">
<i class="bi bi-x-circle me-1"></i> Reject Contract
</button>
<button class="btn btn-secondary">
<i class="bi bi-send me-1"></i> Resend Invitations
</button>
{% elif contract.status == 'Signed' %}
<button class="btn btn-primary">
<i class="bi bi-download me-1"></i> Download Contract
</button>
<button class="btn btn-outline-secondary">
<i class="bi bi-files me-1"></i> Clone Contract
</button>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- Contract Tabs -->
<ul class="nav nav-tabs mb-4" id="contractTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="document-tab" data-bs-toggle="tab" data-bs-target="#document" type="button" role="tab" aria-controls="document" aria-selected="true">
<i class="bi bi-file-text me-1"></i> Document
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="details-tab" data-bs-toggle="tab" data-bs-target="#details" type="button" role="tab" aria-controls="details" aria-selected="false">
<i class="bi bi-info-circle me-1"></i> Details
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="activity-tab" data-bs-toggle="tab" data-bs-target="#activity" type="button" role="tab" aria-controls="activity" aria-selected="false">
<i class="bi bi-clock-history me-1"></i> Activity
</button>
</li>
</ul>
<!-- Contract Content -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Contract Content</h5>
{% if contract.revisions|length > 1 %}
<div class="dropdown">
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" id="versionDropdown" data-bs-toggle="dropdown" aria-expanded="false">
Version {{ contract.current_version }}
</button>
<ul class="dropdown-menu" aria-labelledby="versionDropdown">
{% for revision in contract.revisions %}
<li><a class="dropdown-item" href="/contracts/{{ contract.id }}?version={{ revision.version }}">Version {{ revision.version }}</a></li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
<div class="card-body">
{% if contract.revisions|length > 0 %}
{% set latest_revision = contract.latest_revision %}
<div class="contract-content border p-3 bg-light">
{{ latest_revision.content|safe }}
</div>
<div class="mt-3 text-muted small">
<p>Last updated by {{ latest_revision.created_by }} on {{ latest_revision.created_at }}</p>
{% if latest_revision.comments %}
<p><strong>Comments:</strong> {{ latest_revision.comments }}</p>
<div class="tab-content" id="contractTabsContent">
<!-- Document Tab -->
<div class="tab-pane fade show active" id="document" role="tabpanel" aria-labelledby="document-tab">
<div class="row">
<div class="col-md-9">
<!-- Document View -->
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Contract Document</h5>
{% if contract.status == 'Signed' %}
<span class="badge bg-success">SIGNED</span>
{% elif contract.status == 'Active' %}
<span class="badge bg-success">ACTIVE</span>
{% elif contract.status == 'PendingSignatures' %}
<span class="badge bg-warning text-dark">PENDING</span>
{% elif contract.status == 'Draft' %}
<span class="badge bg-secondary">DRAFT</span>
{% endif %}
</div>
{% else %}
<div class="text-center py-3 text-muted">
<p>No content has been added to this contract yet.</p>
{% if contract.status == 'Draft' %}
<a href="/contracts/{{ contract.id }}/edit" class="btn btn-sm btn-primary">
<i class="bi bi-pencil me-1"></i> Add Content
</a>
<div class="card-body bg-light">
{% if contract.revisions|length > 0 %}
{% set latest_revision = contract.latest_revision %}
<div class="bg-white p-4 border rounded">
{{ latest_revision.content|safe }}
</div>
{% else %}
<div class="text-center py-5 text-muted">
<p>No content has been added to this contract yet.</p>
</div>
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Signers -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Signers</h5>
{% if contract.status == 'Draft' %}
<button class="btn btn-sm btn-primary">
<i class="bi bi-plus me-1"></i> Add Signer
</button>
{% endif %}
</div>
<div class="card-body">
{% if contract.signers|length > 0 %}
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Status</th>
<th>Signed Date</th>
<th>Comments</th>
{% if contract.status == 'Draft' %}
<th>Actions</th>
{% endif %}
</tr>
</thead>
<tbody>
</div>
<!-- Signature Areas -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Signatures</h5>
</div>
<div class="card-body">
<div class="row">
{% for signer in contract.signers %}
<tr>
<td>{{ signer.name }}</td>
<td>{{ signer.email }}</td>
<td>
<span class="badge {% if signer.status == 'Signed' %}bg-success{% elif signer.status == 'Rejected' %}bg-danger{% else %}bg-warning text-dark{% endif %}">
{{ signer.status }}
</span>
</td>
<td>
{% if signer.signed_at %}
{{ signer.signed_at }}
{% else %}
-
{% endif %}
</td>
<td>
{% if signer.comments %}
{{ signer.comments }}
{% else %}
-
{% endif %}
</td>
{% if contract.status == 'Draft' %}
<td>
<button class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash"></i>
</button>
</td>
{% endif %}
</tr>
<div class="col-md-6 mb-4">
<div class="card h-100 {% if signer.status == 'Signed' %}border-success{% elif signer.status == 'Rejected' %}border-danger{% else %}border-warning{% endif %}">
<div class="card-header d-flex justify-content-between align-items-center">
<h6 class="mb-0">{{ signer.name }}</h6>
<span class="badge {% if signer.status == 'Signed' %}bg-success{% elif signer.status == 'Rejected' %}bg-danger{% else %}bg-warning text-dark{% endif %}">
{{ signer.status }}
</span>
</div>
<div class="card-body">
<p class="text-muted mb-2">{{ signer.email }}</p>
{% if signer.status == 'Signed' %}
<div class="text-center border-top pt-3">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e4/Signature_of_John_Hancock.svg/1280px-Signature_of_John_Hancock.svg.png" alt="Signature" class="img-fluid" style="max-height: 60px;">
<div class="small text-muted mt-2">Signed on {{ signer.signed_at }}</div>
</div>
{% elif signer.status == 'Rejected' %}
<div class="alert alert-danger mt-3">
<i class="bi bi-x-circle me-2"></i> Rejected on {{ signer.signed_at }}
</div>
{% else %}
<div class="text-center mt-3">
<p class="text-muted mb-2">Waiting for signature...</p>
{% if not user_has_signed %}
<button class="btn btn-primary btn-sm btn-sign" data-signer-id="{{ signer.id }}">
<i class="bi bi-pen me-1"></i> Sign Here
</button>
{% endif %}
</div>
{% endif %}
{% if signer.comments %}
<div class="mt-3">
<p class="small text-muted mb-1">Comments:</p>
<p class="small">{{ signer.comments }}</p>
</div>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% else %}
<div class="text-center py-3 text-muted">
<p>No signers have been added to this contract yet.</p>
{% if contract.status == 'Draft' %}
<button class="btn btn-sm btn-primary">
<i class="bi bi-plus me-1"></i> Add Signer
</button>
{% endif %}
</div>
<div class="col-md-3">
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Actions</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
{% if contract.status == 'Draft' %}
<a href="/contracts/{{ contract.id }}/edit" class="btn btn-primary">
<i class="bi bi-pencil me-1"></i> Edit Contract
</a>
<a href="/contracts/{{ contract.id }}/send" class="btn btn-success">
<i class="bi bi-send me-1"></i> Send for Signatures
</a>
<button class="btn btn-danger">
<i class="bi bi-trash me-1"></i> Delete Contract
</button>
{% elif contract.status == 'PendingSignatures' %}
<button class="btn btn-success">
<i class="bi bi-pen me-1"></i> Sign Contract
</button>
<button class="btn btn-warning">
<i class="bi bi-x-circle me-1"></i> Reject Contract
</button>
<button class="btn btn-secondary">
<i class="bi bi-send me-1"></i> Resend Invitations
</button>
{% elif contract.status == 'Signed' or contract.status == 'Active' %}
<button class="btn btn-primary">
<i class="bi bi-download me-1"></i> Download Contract
</button>
<button class="btn btn-outline-secondary">
<i class="bi bi-files me-1"></i> Clone Contract
</button>
{% endif %}
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Signers Status</h5>
</div>
<div class="card-body p-0">
<ul class="list-group list-group-flush">
{% for signer in contract.signers %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
<div>{{ signer.name }}</div>
<small class="text-muted">{{ signer.email }}</small>
</div>
<span class="badge {% if signer.status == 'Signed' %}bg-success{% elif signer.status == 'Rejected' %}bg-danger{% else %}bg-warning text-dark{% endif %}">
{{ signer.status }}
</span>
</li>
{% endfor %}
</ul>
</div>
<div class="card-footer">
<div class="d-flex justify-content-between">
<span>Total: {{ contract.signers|length }}</span>
<span>Signed: {{ signed_signers }} / Pending: {{ pending_signers }}</span>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Contract Info</h5>
</div>
<div class="card-body">
<p><strong>Status:</strong>
<span class="badge {% if contract.status == 'Signed' or contract.status == 'Active' %}bg-success{% elif contract.status == 'PendingSignatures' %}bg-warning text-dark{% elif contract.status == 'Draft' %}bg-secondary{% elif contract.status == 'Expired' %}bg-danger{% else %}bg-dark{% endif %}">
{{ contract.status }}
</span>
</p>
<p><strong>Type:</strong> {{ contract.contract_type }}</p>
<p><strong>Created:</strong> {{ contract.created_at }}</p>
<p><strong>Version:</strong> {{ contract.current_version }}</p>
{% if contract.effective_date %}
<p><strong>Effective:</strong> {{ contract.effective_date }}</p>
{% endif %}
{% if contract.expiration_date %}
<p><strong>Expires:</strong> {{ contract.expiration_date }}</p>
{% endif %}
{% if contract.organization %}
<p><strong>Organization:</strong> {{ contract.organization }}</p>
{% endif %}
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Contract Revisions -->
<div class="card mt-4">
<div class="card-header">
<h5 class="mb-0">Contract Revisions</h5>
</div>
<div class="card-body">
{% if contract.revisions|length > 0 %}
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Version</th>
<th>Date</th>
<th>Notes</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for revision in contract.revisions %}
<tr>
<td>{{ revision.version }}</td>
<td>{{ revision.created_at }}</td>
<td>{{ revision.notes }}</td>
<td>
<a href="/contracts/{{ contract.id }}/revisions/{{ revision.version }}" class="btn btn-sm btn-primary">
<i class="bi bi-eye"></i> View
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- Details Tab -->
<div class="tab-pane fade" id="details" role="tabpanel" aria-labelledby="details-tab">
<div class="row">
<div class="col-md-8">
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Contract Overview</h5>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-3 fw-bold">Status:</div>
<div class="col-md-9">
<span class="badge {% if contract.status == 'Signed' or contract.status == 'Active' %}bg-success{% elif contract.status == 'PendingSignatures' %}bg-warning text-dark{% elif contract.status == 'Draft' %}bg-secondary{% elif contract.status == 'Expired' %}bg-danger{% else %}bg-dark{% endif %}">
{{ contract.status }}
</span>
</div>
</div>
<div class="row mb-3">
<div class="col-md-3 fw-bold">Type:</div>
<div class="col-md-9">{{ contract.contract_type }}</div>
</div>
<div class="row mb-3">
<div class="col-md-3 fw-bold">Created By:</div>
<div class="col-md-9">{{ contract.created_by }}</div>
</div>
<div class="row mb-3">
<div class="col-md-3 fw-bold">Created:</div>
<div class="col-md-9">{{ contract.created_at }}</div>
</div>
<div class="row mb-3">
<div class="col-md-3 fw-bold">Last Updated:</div>
<div class="col-md-9">{{ contract.updated_at }}</div>
</div>
{% if contract.effective_date %}
<div class="row mb-3">
<div class="col-md-3 fw-bold">Effective Date:</div>
<div class="col-md-9">{{ contract.effective_date }}</div>
</div>
{% endif %}
{% if contract.expiration_date %}
<div class="row mb-3">
<div class="col-md-3 fw-bold">Expiration Date:</div>
<div class="col-md-9">{{ contract.expiration_date }}</div>
</div>
{% endif %}
<div class="row mb-3">
<div class="col-md-3 fw-bold">Version:</div>
<div class="col-md-9">{{ contract.current_version }}</div>
</div>
<div class="row">
<div class="col-md-3 fw-bold">Description:</div>
<div class="col-md-9">{{ contract.description }}</div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Organization</h5>
</div>
<div class="card-body">
{% if contract.organization %}
<p><strong>{{ contract.organization }}</strong></p>
<p class="text-muted">
<i class="bi bi-building me-1"></i> Registered in Zanzibar Autonomous Zone
</p>
{% else %}
<p class="text-muted">No organization specified</p>
{% endif %}
</div>
</div>
</div>
</div>
{% else %}
<p class="text-muted">No revisions available.</p>
{% endif %}
</div>
</div>
<!-- Revision History -->
<div class="row">
<div class="col-12">
<div class="card">
<!-- Contract Revisions -->
<div class="card mb-4">
<div class="card-header">
<h5 class="mb-0">Revision History</h5>
<h5 class="mb-0">Contract Revisions</h5>
</div>
<div class="card-body">
{% if contract.revisions|length > 0 %}
<div class="table-responsive">
<table class="table">
<table class="table table-striped">
<thead>
<tr>
<th>Version</th>
<th>Date</th>
<th>Created By</th>
<th>Created Date</th>
<th>Comments</th>
<th>Notes</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for revision in contract.revisions|reverse %}
{% for revision in contract.revisions %}
<tr>
<td>{{ revision.version }}</td>
<td>{{ revision.created_by }}</td>
<td>{{ revision.created_at }}</td>
<td>{{ revision.created_by }}</td>
<td>{{ revision.notes }}</td>
<td>
{% if revision.comments %}
{{ revision.comments }}
{% else %}
-
{% endif %}
</td>
<td>
<a href="/contracts/{{ contract.id }}?version={{ revision.version }}" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-eye"></i>
<a href="/contracts/{{ contract.id }}?version={{ revision.version }}" class="btn btn-sm btn-primary">
<i class="bi bi-eye"></i> View
</a>
</td>
</tr>
@ -328,13 +335,213 @@
</table>
</div>
{% else %}
<div class="text-center py-3 text-muted">
<p>No revisions have been made to this contract yet.</p>
</div>
<p class="text-muted">No revisions available.</p>
{% endif %}
</div>
</div>
</div>
<!-- Activity Tab -->
<div class="tab-pane fade" id="activity" role="tabpanel" aria-labelledby="activity-tab">
<div class="card">
<div class="card-header">
<h5 class="mb-0">Activity Timeline</h5>
</div>
<div class="card-body">
<ul class="list-group">
<li class="list-group-item border-start border-4 border-primary">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1">Contract Created</h6>
<small>{{ contract.created_at }}</small>
</div>
<p class="mb-1">{{ contract.created_by }} created this contract</p>
</li>
{% for revision in contract.revisions %}
{% if revision.version > 1 %}
<li class="list-group-item border-start border-4 border-info">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1">Contract Updated to Version {{ revision.version }}</h6>
<small>{{ revision.created_at }}</small>
</div>
<p class="mb-1">{{ revision.created_by }} updated the contract</p>
{% if revision.notes %}
<p class="mb-0 text-muted fst-italic">{{ revision.notes }}</p>
{% endif %}
</li>
{% endif %}
{% endfor %}
{% for signer in contract.signers %}
{% if signer.status == 'Signed' %}
<li class="list-group-item border-start border-4 border-success">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1">Contract Signed</h6>
<small>{{ signer.signed_at }}</small>
</div>
<p class="mb-1">{{ signer.name }} signed the contract</p>
{% if signer.comments %}
<p class="mb-0 text-muted fst-italic">{{ signer.comments }}</p>
{% endif %}
</li>
{% endif %}
{% endfor %}
{% if contract.status == 'Active' %}
<li class="list-group-item border-start border-4 border-success">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1">Contract Activated</h6>
<small>{{ contract.updated_at }}</small>
</div>
<p class="mb-1">Contract became active after all parties signed</p>
</li>
{% endif %}
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- Signature Modal -->
<div class="modal fade" id="signatureModal" tabindex="-1" aria-labelledby="signatureModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="signatureModalLabel">Sign Contract</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="signatureCanvas" class="form-label">Draw your signature below:</label>
<div class="border p-2">
<canvas id="signatureCanvas" width="450" height="150" style="border: 1px solid #ddd; width: 100%;"></canvas>
</div>
<div class="mt-2 text-end">
<button class="btn btn-sm btn-outline-secondary" id="clearSignature">Clear</button>
</div>
</div>
<div class="mb-3">
<label for="signatureComments" class="form-label">Comments (optional):</label>
<textarea class="form-control" id="signatureComments" rows="3"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="submitSignature">Sign Contract</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Signature canvas functionality
const canvas = document.getElementById('signatureCanvas');
const ctx = canvas.getContext('2d');
let isDrawing = false;
let lastX = 0;
let lastY = 0;
// Set up canvas
ctx.lineWidth = 2;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.strokeStyle = '#000';
// Drawing functions
function startDrawing(e) {
isDrawing = true;
[lastX, lastY] = [e.offsetX, e.offsetY];
}
function draw(e) {
if (!isDrawing) return;
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
[lastX, lastY] = [e.offsetX, e.offsetY];
}
function stopDrawing() {
isDrawing = false;
}
// Event listeners for canvas
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);
canvas.addEventListener('mouseout', stopDrawing);
// Touch support
canvas.addEventListener('touchstart', function(e) {
e.preventDefault();
const touch = e.touches[0];
const mouseEvent = new MouseEvent('mousedown', {
clientX: touch.clientX,
clientY: touch.clientY
});
canvas.dispatchEvent(mouseEvent);
});
canvas.addEventListener('touchmove', function(e) {
e.preventDefault();
const touch = e.touches[0];
const mouseEvent = new MouseEvent('mousemove', {
clientX: touch.clientX,
clientY: touch.clientY
});
canvas.dispatchEvent(mouseEvent);
});
canvas.addEventListener('touchend', function(e) {
e.preventDefault();
const mouseEvent = new MouseEvent('mouseup', {});
canvas.dispatchEvent(mouseEvent);
});
// Clear signature
document.getElementById('clearSignature').addEventListener('click', function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
});
// Sign buttons
const signButtons = document.querySelectorAll('.btn-sign');
let currentSignerId = null;
signButtons.forEach(button => {
button.addEventListener('click', function() {
currentSignerId = this.dataset.signerId;
const signatureModal = new bootstrap.Modal(document.getElementById('signatureModal'));
signatureModal.show();
});
});
// Submit signature
document.getElementById('submitSignature').addEventListener('click', function() {
// In a real app, we would send the signature to the server
// For demo, we'll just simulate a successful signature
// Get the signature image
const signatureImage = canvas.toDataURL();
const comments = document.getElementById('signatureComments').value;
// Close the modal
const signatureModal = bootstrap.Modal.getInstance(document.getElementById('signatureModal'));
signatureModal.hide();
// Show success message
alert('Contract signed successfully!');
// Reload the page to show the updated contract
// In a real app, we would update the UI without reloading
setTimeout(() => {
window.location.reload();
}, 1000);
});
});
</script>
{% endblock %}