Skip to content

Commit

Permalink
Merge pull request #42 from onicai/main
Browse files Browse the repository at this point in the history
Merge newest changes
  • Loading branch information
patnorris authored Apr 9, 2024
2 parents c2022af + 24b7382 commit 5df3d09
Show file tree
Hide file tree
Showing 27 changed files with 811 additions and 45 deletions.
17 changes: 17 additions & 0 deletions backend/donation_tracker_canister/src/Main.mo
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ actor class DonationTracker(_donation_canister_id : Text) {
name = "Green Valley High";
thumbnail = "./images/school1_thumbnail.png";
address = "123 Green Valley Rd";
wallets = [];
};

let student1School1 : Types.Recipient =
Expand All @@ -114,6 +115,7 @@ actor class DonationTracker(_donation_canister_id : Text) {
thumbnail = "./images/student1School1_thumbnail.png";
grade = 10;
schoolId = "school1";
wallets = [];
};

let student2School1 : Types.Recipient =
Expand All @@ -123,6 +125,7 @@ actor class DonationTracker(_donation_canister_id : Text) {
thumbnail = "./images/student2School1_thumbnail.png";
grade = 11;
schoolId = "school1";
wallets = [];
};

let school2 : Types.Recipient =
Expand All @@ -131,6 +134,7 @@ actor class DonationTracker(_donation_canister_id : Text) {
name = "Sunnydale Elementary";
thumbnail = "./images/school2_thumbnail.png";
address = "456 Sunnydale St";
wallets = [];
};

let student1School2 : Types.Recipient =
Expand All @@ -140,6 +144,7 @@ actor class DonationTracker(_donation_canister_id : Text) {
thumbnail = "./images/student1School2_thumbnail.png";
grade = 8;
schoolId = "school2";
wallets = [];
};

let student2School2 : Types.Recipient =
Expand All @@ -149,6 +154,10 @@ actor class DonationTracker(_donation_canister_id : Text) {
thumbnail = "./images/student2School2_thumbnail.png";
grade = 9;
schoolId = "school2";
wallets = [{
walletType : Types.WalletType = #CKBTC;
address : Text = "fsmbm-odyjn-hkwt2-3be4e-h6bg3-yi3pi-f5eny-2rosh-4u6jm-3rwa5-xae";
}]; // example ckBTC address
};

// (re)Initialize recipientsById HashMap
Expand Down Expand Up @@ -494,6 +503,10 @@ actor class DonationTracker(_donation_canister_id : Text) {
return #Err(#Other("Failed to retrieve BTC donation address for DONATION_CANISTER_ID = " # DONATION_CANISTER_ID));
};
};
case (#CKBTC) {
// There is no central ckBTC donation address (as it's peer-to-peer)
return #Err(#Other("There is no central ckBTC donation wallet address, the donations are peer-to-peer."));
};
// Handle other payment types as they are added
};
};
Expand All @@ -515,6 +528,10 @@ actor class DonationTracker(_donation_canister_id : Text) {
return #Err(#Other("Failed to retrieve total donation amount for BTC for DONATION_CANISTER_ID = " # DONATION_CANISTER_ID));
};
};
case (#CKBTC) {
// There is no central ckBTC donation address to check for here (as it's peer-to-peer)
return #Err(#Other("There is no central ckBTC donation wallet address that collects the total amount, the donations are peer-to-peer."));
};
// Handle other payment types as they are added
};
};
Expand Down
57 changes: 55 additions & 2 deletions backend/donation_tracker_canister/src/Types.mo
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,26 @@ module Types {

public type RecipientsResult = Result<RecipientOverviewsRecord, ApiError>;

public type SchoolInfo = RecipientOverview and {
public type WalletType = {
#BTC;
#CKBTC;
};

public type RecipientWallet = {
walletType : WalletType;
address : Text;
};

public type RecipientDetails = RecipientOverview and {
wallets : [RecipientWallet];
};

public type SchoolInfo = RecipientDetails and {
address : Text;
// Add more school-specific fields as necessary
};

public type StudentInfo = RecipientOverview and {
public type StudentInfo = RecipientDetails and {
grade : Nat;
schoolId : Text;
// Add more student-specific fields as necessary
Expand All @@ -173,6 +187,7 @@ module Types {
public type RecipientResult = Result<?RecipientRecord, ApiError>;

//-------------------------------------------------------------------------
// for Bitcoin (BTC)
public type BitcoinTransaction = {
bitcoinTransactionId : PaymentTransactionId;
totalValue : Nat64; // Total value of the BTC transaction
Expand All @@ -191,8 +206,46 @@ module Types {
public type BitcoinTransactionResult = Result<BitcoinTransactionRecord, ApiError>;

//-------------------------------------------------------------------------
// for ckBTC

// from https://raw.githubusercontent.com/dfinity/ic/95787355499f3be929f2ab302ed44cdad7c64061/rs/rosetta-api/icrc1/ledger/ledger.did
public type Subaccount = Blob;

public type Account = {
owner : Principal;
subaccount : ?Subaccount;
};

public type CKBTCTransaction = {
kind : Text;
mint : ?{
amount : Nat;
to : Account;
memo : ?Blob;
created_at_time : ?Nat64;
};
burn : ?{
amount : Nat;
from : Account;
memo : ?Blob;
created_at_time : ?Nat64;
};
transfer : ?{
amount : Nat;
from : Account;
to : Account;
memo : ?Blob;
created_at_time : ?Nat64;
fee : ?Nat;
};
timestamp : Nat64;
};

//-------------------------------------------------------------------------

public type PaymentType = {
#BTC;
#CKBTC;
// Future payment types can be added here as new variants.
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,80 @@
<script lang="ts">
import type { Donation } from "src/declarations/donation_tracker_canister/donation_tracker_canister.did";
import { store, currentDonationCreationObject } from "../../store";
import { store, currentDonationCreationObject } from "../../../store";
import { push } from "svelte-spa-router";
import spinner from "../../assets/loading.gif";
import spinner from "../../../assets/loading.gif";
import { calculateCurrencyUnitAddition } from '../../../helpers/utils.js';
let validationErrors = [];
let confirmNewTotal = false;
let currencyUnitText = $currentDonationCreationObject.donation.currencyUnitText;
let needsCurrencyUnitAddition = $currentDonationCreationObject.donation.needsCurrencyUnitAddition;
let transactionInfoToDisplay = "";
let finalPaymentType = { BTC: null }; // set BTC as default
let finalPaymentTransactionId = "";
// Function to validate the donation details
function validateDonationDetails() {
validationErrors = []; // Reset errors
$currentDonationCreationObject.bitcoinTransaction.bitcoinTransactionId || validationErrors.push('Bitcoin transaction ID is missing. Please provide a Bitcoin Transaction Id on the Transaction step.');
if ($currentDonationCreationObject.bitcoinTransaction.valueLeftToDonate <= 0) {
validationErrors.push('There is no BTC left to donate on this transaction. Please use another Bitcoin Transaction Id on the Transaction step.');
};
$currentDonationCreationObject.recipient.recipientId || validationErrors.push('Recipient ID is missing. Please select a recipient on the Recipient step.');
if ($currentDonationCreationObject.donation.totalDonation <= 0) {
validationErrors.push('Total donation must be greater than 0. Please adjust your donation on the Donation step.');
};
if ($currentDonationCreationObject.donation.totalDonation > $currentDonationCreationObject.bitcoinTransaction.valueLeftToDonate) {
validationErrors.push('Total donation cannot be greater than the amount left to donate on the transaction. Please adjust your donation on the Donation step.');
};
validatePaymentTypeSpecifics();
// Validate categorySplit sums up to total donation
const totalSum = Object.values($currentDonationCreationObject.donation.categorySplit).reduce((total, percent) => total + Number(percent), 0);
if (totalSum !== $currentDonationCreationObject.donation.totalDonation) {
$currentDonationCreationObject.donation.totalDonation = totalSum;
confirmNewTotal = true;
};
}
};
// Some checks depend on selected payment type
function validatePaymentTypeSpecifics() {
switch($currentDonationCreationObject.donation.paymentType) {
case 'BTC':
validateBTCSpecifics();
break;
case 'ckBTC':
validateCKBTCSpecifics();
break;
// Add cases for other supported payment types here
default:
validationErrors.push('Unsupported payment type. Please change your selection.');
};
};
// Specific checks for BTC as selected payment type
function validateBTCSpecifics() {
$currentDonationCreationObject.bitcoinTransaction.bitcoinTransactionId || validationErrors.push('Bitcoin transaction ID is missing. Please provide a Bitcoin Transaction Id on the Transaction step.');
if ($currentDonationCreationObject.bitcoinTransaction.valueLeftToDonate <= 0) {
validationErrors.push('There is no BTC left to donate on this transaction. Please use another Bitcoin Transaction Id on the Transaction step.');
};
if ($currentDonationCreationObject.donation.totalDonation > $currentDonationCreationObject.bitcoinTransaction.valueLeftToDonate) {
validationErrors.push('Total donation cannot be greater than the amount left to donate on the transaction. Please adjust your donation on the Donation step.');
};
// Set any values to BTC specifics
transactionInfoToDisplay = `Bitcoin Transaction ID: ${$currentDonationCreationObject.bitcoinTransaction.bitcoinTransactionId}`;
finalPaymentType = { BTC: null };
finalPaymentTransactionId = $currentDonationCreationObject.bitcoinTransaction.bitcoinTransactionId;
};
// Specific checks for ckBTC as selected payment type
function validateCKBTCSpecifics() {
// Set any values to ckBTC specifics
transactionInfoToDisplay = `Please make sure your ckBTC transaction was successful.`;
finalPaymentType = { CKBTC: null };
};
// Call validation on component mount
validateDonationDetails();
Expand All @@ -53,21 +95,28 @@
// Result<{dti : DTI}, ApiError>;
const totalAmountConverted = BigInt($currentDonationCreationObject.donation.totalDonation);
let categorySplit = $currentDonationCreationObject.donation.categorySplit;
let categorySplitConverted = {
curriculumDesign: BigInt(0.0),
teacherSupport: BigInt(0.0),
schoolSupplies: BigInt(0.0),
lunchAndSnacks: BigInt(0.0),
};
for (let category in categorySplit) {
categorySplitConverted[category] = BigInt(categorySplit[category].amount);
};
const finalDonation : Donation = {
totalAmount: totalAmountConverted,
allocation: $currentDonationCreationObject.donation.categorySplit,
paymentTransactionId: $currentDonationCreationObject.bitcoinTransaction.bitcoinTransactionId,
paymentType: {
BTC: null
},
allocation: categorySplitConverted,
paymentTransactionId: finalPaymentTransactionId, // This might be filled by the backend, depending on the payment type
paymentType: finalPaymentType,
personalNote: [$currentDonationCreationObject.donation.personalNote],
recipientId: $currentDonationCreationObject.recipient.recipientId,
// The following fields will be updated by the backend
timestamp: 0n,
dti: 0n,
rewardsHaveBeenClaimed: false,
hasBeenDistributed: [false],
donor: {
Anonymous: null
},
Expand Down Expand Up @@ -110,15 +159,15 @@
<div class="space-y-2 text-gray-800 dark:text-gray-200">
<p class="mt-4">Let's double-check that all donation details are looking good.</p>
<span class="inline-block break-all">
<p>Bitcoin Transaction ID: {$currentDonationCreationObject.bitcoinTransaction.bitcoinTransactionId}</p>
<p>{transactionInfoToDisplay}</p>
</span>
<p>Recipient Name: {$currentDonationCreationObject.recipient.recipientInfo?.name}</p>
<p>Total Donation: {$currentDonationCreationObject.donation.totalDonation} {$currentDonationCreationObject.donation.paymentType === "BTC" ? "Satoshi" : ""}</p>
<p>Total Donation: {$currentDonationCreationObject.donation.totalDonation} {currencyUnitText} {needsCurrencyUnitAddition ? calculateCurrencyUnitAddition($currentDonationCreationObject.donation.paymentType, $currentDonationCreationObject.donation.totalDonation) : ""}</p>
<p>Payment Type: {$currentDonationCreationObject.donation.paymentType}</p>
<p>Category Split:</p>
<ul>
{#each Object.entries($currentDonationCreationObject.donation.categorySplit) as [category, btc]}
<li class="py-1">{category}: {btc} Satoshi</li>
{#each Object.entries($currentDonationCreationObject.donation.categorySplit) as [category, amount]}
<li class="py-1">{category}: {amount} {currencyUnitText} {needsCurrencyUnitAddition ? calculateCurrencyUnitAddition($currentDonationCreationObject.donation.paymentType, amount) : ""}</li>
{/each}
</ul>
{#if confirmNewTotal}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script lang="ts">
import { currentDonationCreationObject } from "../../../store";
import PaymentTypeInfo from "./PaymentTypeInfo.svelte"; // This component displays info on the selected paymentType
import PaymentOptions from './PaymentOptions.svelte';
</script>

<section class="bg-white dark:bg-gray-900 bg-[url('/images/hero-pattern.svg')]" >
<div class="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16 z-10 relative">
<h1 class="mb-4 text-4xl font-extrabold tracking-tight leading-none text-gray-900 md:text-5xl lg:text-6xl dark:text-white">
Select Currency</h1>

<p class="mt-4 text-black dark:text-white font-semibold">Currently selected payment type: {$currentDonationCreationObject.donation.paymentType}</p>
<p class="mt-2 text-black dark:text-white">You can change the payment type. Options are available below.</p>

<div class="py-4 mt-4 space-y-3 bg-blue-50 dark:bg-blue-800 shadow-md border border-gray-300 dark:border-gray-700 rounded-lg">
<PaymentTypeInfo />
</div>

<PaymentOptions />
</div>
</section>
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<script lang="ts">
import { currentDonationCreationObject, supportedPaymentTypes } from "../../../store";
// Reactive statement to ensure we react to changes in the current donation creation object
$: currentDonation = $currentDonationCreationObject;
function updatePaymentType(type) {
currentDonationCreationObject.update((currentDonation) => {
currentDonation.donation.paymentType = type;
switch(type) {
case 'BTC':
currentDonation.donation.currencyUnitText = "Satoshi";
currentDonation.donation.needsCurrencyUnitAddition = true;
break;
case 'ckBTC':
currentDonation.donation.currencyUnitText = "ckSatoshi";
currentDonation.donation.needsCurrencyUnitAddition = true;
break;
// Add cases for other supported payment types here
};
return currentDonation;
});
}
// Determine available payment types based on peerToPeerPayment flag
$: availablePaymentTypes = currentDonation.donation.peerToPeerPayment
? Object.entries(currentDonation.recipient.recipientWalletAddresses).filter(([type, address]) => address !== null && address !== "")
: Object.entries(currentDonation.donationWalletAddresses).filter(([type, address]) => address !== null && address !== "");
// Extract just the payment types for easier checking against supportedPaymentTypes
$: enabledPaymentTypes = availablePaymentTypes.map(([type]) => type);
</script>

<div class="mt-4">
{#each $supportedPaymentTypes as type}
{#if enabledPaymentTypes.includes(type)}
<button
on:click|preventDefault={() => updatePaymentType(type)}
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded m-2">
Select {type}
</button>
{:else}
<button
disabled
class="bg-gray-500 text-white font-bold py-2 px-4 rounded m-2">
{type} (Unavailable)
</button>
{/if}
{/each}
</div>
Loading

0 comments on commit 5df3d09

Please sign in to comment.