From 86a29291819a10ce93c9f234a56f025dd41bf4a9 Mon Sep 17 00:00:00 2001 From: Amobi Ndubuisi Date: Fri, 30 Aug 2024 06:35:14 -0700 Subject: [PATCH] Updated the UI and made the UI more robust --- donorfront/src/App.js | 42 +++-- donorfront/src/UserDashboard.js | 0 donorfront/src/components/AdminPanel.js | 160 ++++++++++-------- .../src/components/BeneficiaryDetails.js | 0 donorfront/src/components/BeneficiaryList.js | 64 +++++-- donorfront/src/components/DonationForm.js | 74 +++++--- donorfront/src/components/Navigation.js | 23 +++ donorfront/src/components/Statistics.js | 43 +++++ donorfront/src/components/UserDashboard.js | 48 ++++++ donorfront/src/components/UtilizationList.js | 2 +- donorfront/src/components/common/Button.js | 0 donorfront/src/components/common/Input.js | 0 .../src/components/common/LoadingSpinner.js | 0 donorfront/src/components/common/Modal.js | 0 donorfront/src/config.js | 0 donorfront/src/contexts/UserContext.js | 0 donorfront/src/contexts/Web3Context.js | 0 donorfront/src/contracts/CharityContract.js | 0 donorfront/src/hooks/useAuth.js | 41 +++++ donorfront/src/hooks/useSmartContract.js | 0 donorfront/src/hooks/useWeb3.js | 0 .../src/styles/components/AdminPanel.css | 0 .../styles/components/BeneficiaryDetails.css | 0 .../src/styles/components/BeneficiaryList.css | 0 .../src/styles/components/DonationForm.css | 0 .../src/styles/components/Navigation.css | 0 .../src/styles/components/UserDashboard.css | 0 .../src/styles/components/UtilizationList.css | 0 donorfront/src/styles/global.css | 0 donorfront/src/utils/contractHelpers.js | 73 ++++++++ donorfront/src/utils/formatters.js | 0 donorfront/src/utils/validators.js | 0 32 files changed, 444 insertions(+), 126 deletions(-) create mode 100644 donorfront/src/UserDashboard.js create mode 100644 donorfront/src/components/BeneficiaryDetails.js create mode 100644 donorfront/src/components/Navigation.js create mode 100644 donorfront/src/components/Statistics.js create mode 100644 donorfront/src/components/UserDashboard.js create mode 100644 donorfront/src/components/common/Button.js create mode 100644 donorfront/src/components/common/Input.js create mode 100644 donorfront/src/components/common/LoadingSpinner.js create mode 100644 donorfront/src/components/common/Modal.js create mode 100644 donorfront/src/config.js create mode 100644 donorfront/src/contexts/UserContext.js create mode 100644 donorfront/src/contexts/Web3Context.js create mode 100644 donorfront/src/contracts/CharityContract.js create mode 100644 donorfront/src/hooks/useAuth.js create mode 100644 donorfront/src/hooks/useSmartContract.js create mode 100644 donorfront/src/hooks/useWeb3.js create mode 100644 donorfront/src/styles/components/AdminPanel.css create mode 100644 donorfront/src/styles/components/BeneficiaryDetails.css create mode 100644 donorfront/src/styles/components/BeneficiaryList.css create mode 100644 donorfront/src/styles/components/DonationForm.css create mode 100644 donorfront/src/styles/components/Navigation.css create mode 100644 donorfront/src/styles/components/UserDashboard.css create mode 100644 donorfront/src/styles/components/UtilizationList.css create mode 100644 donorfront/src/styles/global.css create mode 100644 donorfront/src/utils/contractHelpers.js create mode 100644 donorfront/src/utils/formatters.js create mode 100644 donorfront/src/utils/validators.js diff --git a/donorfront/src/App.js b/donorfront/src/App.js index 46aebad..e8671db 100644 --- a/donorfront/src/App.js +++ b/donorfront/src/App.js @@ -1,31 +1,43 @@ import React, { useState, useEffect } from 'react'; +import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; +import Navigation from './Navigation'; import BeneficiaryList from './BeneficiaryList'; +import BeneficiaryDetails from './BeneficiaryDetails'; import DonationForm from './DonationForm'; import UtilizationList from './UtilizationList'; import AdminPanel from './AdminPanel'; -import { useWeb3 } from '../hooks/useWeb3'; -// import { getContractInstance } from '../utils/contractHelpers'; +import UserDashboard from './UserDashboard'; +import { Web3Provider } from '../contexts/Web3Context'; +import { UserProvider } from '../contexts/UserContext'; const App = () => { const [userRole, setUserRole] = useState(null); - const [beneficiaries, setBeneficiaries] = useState([]); - const [donations, setDonations] = useState([]); - const [utilizations, setUtilizations] = useState([]); - const { account, web3 } = useWeb3(); useEffect(() => { - // TODO: Fetch user role, beneficiaries, donations, and utilizations from the smart contract + // TODO: Fetch user role from the smart contract }, []); return ( -
-

Charity Smart Contract UI

- - - - {userRole === 'ROLE_ADMIN' && } -
+ + + +
+ +
+ + + + + + {userRole === 'admin' && } + + +
+
+
+
+
); }; -export default App; +export default App; \ No newline at end of file diff --git a/donorfront/src/UserDashboard.js b/donorfront/src/UserDashboard.js new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/components/AdminPanel.js b/donorfront/src/components/AdminPanel.js index e38956d..08b5d93 100644 --- a/donorfront/src/components/AdminPanel.js +++ b/donorfront/src/components/AdminPanel.js @@ -1,81 +1,107 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; +import { useWeb3 } from '../hooks/useWeb3'; +import { getBeneficiaries, registerBeneficiary, approveUtilization } from '../utils/contractHelpers'; const AdminPanel = () => { - const [beneficiaryName, setBeneficiaryName] = useState(''); - const [beneficiaryDescription, setBeneficiaryDescription] = useState(''); - const [beneficiaryTargetAmount, setBeneficiaryTargetAmount] = useState(''); - const [utilizationBeneficiaryId, setUtilizationBeneficiaryId] = useState(''); - const [utilizationDescription, setUtilizationDescription] = useState(''); - const [utilizationAmount, setUtilizationAmount] = useState(''); + const [beneficiaries, setBeneficiaries] = useState([]); + const [newBeneficiary, setNewBeneficiary] = useState({ name: '', description: '', targetAmount: '' }); + const [utilizationRequests, setUtilizationRequests] = useState([]); + const { contract, address } = useWeb3(); - const handleRegisterBeneficiary = (e) => { - e.preventDefault(); - // TODO: Call the register-beneficiary function from the smart contract - console.log('Register beneficiary:', { beneficiaryName, beneficiaryDescription, beneficiaryTargetAmount }); + useEffect(() => { + fetchBeneficiaries(); + fetchUtilizationRequests(); + }, [contract]); + + const fetchBeneficiaries = async () => { + if (contract) { + const beneficiaryList = await getBeneficiaries(contract); + setBeneficiaries(beneficiaryList); + } + }; + + const fetchUtilizationRequests = async () => { + // TODO: Implement fetching utilization requests from the contract }; - const handleAddUtilization = (e) => { + const handleRegisterBeneficiary = async (e) => { e.preventDefault(); - // TODO: Call the add-utilization function from the smart contract - console.log('Add utilization:', { utilizationBeneficiaryId, utilizationDescription, utilizationAmount }); + try { + await registerBeneficiary(contract, address, newBeneficiary.name, newBeneficiary.description, newBeneficiary.targetAmount); + setNewBeneficiary({ name: '', description: '', targetAmount: '' }); + fetchBeneficiaries(); + } catch (error) { + console.error('Error registering beneficiary:', error); + } + }; + + const handleApproveUtilization = async (beneficiaryId, milestoneId) => { + try { + await approveUtilization(contract, address, beneficiaryId, milestoneId); + fetchUtilizationRequests(); + } catch (error) { + console.error('Error approving utilization:', error); + } }; return ( -
-

Admin Panel

-
-

Register Beneficiary

- setBeneficiaryName(e.target.value)} - placeholder="Name" - required - /> - setBeneficiaryDescription(e.target.value)} - placeholder="Description" - required - /> - setBeneficiaryTargetAmount(e.target.value)} - placeholder="Target Amount" - required - /> - -
+
+

Admin Panel

+ +
+

Register New Beneficiary

+
+ setNewBeneficiary({...newBeneficiary, name: e.target.value})} + className="w-full p-2 border border-gray-300 rounded" + required + /> + + setNewBeneficiary({...newBeneficiary, targetAmount: e.target.value})} + className="w-full p-2 border border-gray-300 rounded" + required + /> + +
+
-
-

Add Utilization

- setUtilizationBeneficiaryId(e.target.value)} - placeholder="Beneficiary ID" - required - /> - setUtilizationDescription(e.target.value)} - placeholder="Description" - required - /> - setUtilizationAmount(e.target.value)} - placeholder="Amount" - required - /> - -
+
+

Utilization Requests

+ {utilizationRequests.length === 0 ? ( +

No pending utilization requests.

+ ) : ( +
    + {utilizationRequests.map((request) => ( +
  • + {request.description} - {request.amount} STX + +
  • + ))} +
+ )} +
); }; -export default AdminPanel; +export default AdminPanel; \ No newline at end of file diff --git a/donorfront/src/components/BeneficiaryDetails.js b/donorfront/src/components/BeneficiaryDetails.js new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/components/BeneficiaryList.js b/donorfront/src/components/BeneficiaryList.js index fd257d1..8297de8 100644 --- a/donorfront/src/components/BeneficiaryList.js +++ b/donorfront/src/components/BeneficiaryList.js @@ -1,23 +1,57 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; +import { Link } from 'react-router-dom'; +import { useWeb3 } from '../hooks/useWeb3'; +import { getBeneficiaries } from '../utils/contractHelpers'; + +const BeneficiaryList = () => { + const [beneficiaries, setBeneficiaries] = useState([]); + const { contract } = useWeb3(); + + useEffect(() => { + const fetchBeneficiaries = async () => { + if (contract) { + const beneficiaryList = await getBeneficiaries(contract); + setBeneficiaries(beneficiaryList); + } + }; + + fetchBeneficiaries(); + }, [contract]); -const BeneficiaryList = ({ beneficiaries }) => { return ( -
-

Beneficiaries

-
    +
    +

    Beneficiaries

    +
    {beneficiaries.map((beneficiary) => ( -
  • -

    {beneficiary.name}

    -

    {beneficiary.description}

    -

    Target Amount: {beneficiary.targetAmount}

    -

    Received Amount: {beneficiary.receivedAmount}

    -

    Status: {beneficiary.status}

    -
  • +
    +
    +

    {beneficiary.name}

    +

    {beneficiary.description}

    +
    + + {beneficiary.receivedAmount} / {beneficiary.targetAmount} STX + + + View Details + +
    +
    +
    +
    +
    +
    +
    +
    ))} -
+
); }; -export default BeneficiaryList; - \ No newline at end of file +export default BeneficiaryList; \ No newline at end of file diff --git a/donorfront/src/components/DonationForm.js b/donorfront/src/components/DonationForm.js index 226e001..5f8f925 100644 --- a/donorfront/src/components/DonationForm.js +++ b/donorfront/src/components/DonationForm.js @@ -1,42 +1,60 @@ import React, { useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { useWeb3 } from '../hooks/useWeb3'; +import { donate } from '../utils/contractHelpers'; -const DonationForm = ({ beneficiaries }) => { - const [selectedBeneficiary, setSelectedBeneficiary] = useState(''); +const DonationForm = () => { + const { id } = useParams(); const [amount, setAmount] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const { contract, address } = useWeb3(); - const handleSubmit = (e) => { + const handleSubmit = async (e) => { e.preventDefault(); - // TODO: Call the donate function from the smart contract - console.log('Donation submitted:', { beneficiaryId: selectedBeneficiary, amount }); + setIsLoading(true); + setError(null); + + try { + await donate(contract, address, id, amount); + // TODO: Show success message and update UI + } catch (err) { + setError(err.message); + } finally { + setIsLoading(false); + } }; return ( -
-

Make a Donation

-
- setAmount(e.target.value)} + className="w-full border border-gray-300 rounded-md shadow-sm p-2 focus:ring-blue-500 focus:border-blue-500" + required + /> +
+ {error &&

{error}

} + + {isLoading ? 'Processing...' : 'Donate'} + ); }; -export default DonationForm; +export default DonationForm; \ No newline at end of file diff --git a/donorfront/src/components/Navigation.js b/donorfront/src/components/Navigation.js new file mode 100644 index 0000000..ff0e22c --- /dev/null +++ b/donorfront/src/components/Navigation.js @@ -0,0 +1,23 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +const Navigation = ({ userRole }) => { + return ( + + ); +}; + +export default Navigation; \ No newline at end of file diff --git a/donorfront/src/components/Statistics.js b/donorfront/src/components/Statistics.js new file mode 100644 index 0000000..386fa56 --- /dev/null +++ b/donorfront/src/components/Statistics.js @@ -0,0 +1,43 @@ +import React from 'react'; + +const StatCard = ({ title, value, icon }) => ( +
+
+ {icon} +
+
+

{title}

+

{value}

+
+
+); + +const Statistics = ({ totalDonations, totalBeneficiaries, totalAmount }) => { + return ( +
+ + + } + /> + + + } + /> + + + } + /> +
+ ); +}; + +export default Statistics; \ No newline at end of file diff --git a/donorfront/src/components/UserDashboard.js b/donorfront/src/components/UserDashboard.js new file mode 100644 index 0000000..336917d --- /dev/null +++ b/donorfront/src/components/UserDashboard.js @@ -0,0 +1,48 @@ +import React, { useState, useEffect } from 'react'; +import { useWeb3 } from '../hooks/useWeb3'; +import { getUserDonations } from '../utils/contractHelpers'; + +const UserDashboard = () => { + const [donations, setDonations] = useState([]); + const [totalDonated, setTotalDonated] = useState(0); + const { contract, address } = useWeb3(); + + useEffect(() => { + fetchUserDonations(); + }, [contract, address]); + + const fetchUserDonations = async () => { + if (contract && address) { + const userDonations = await getUserDonations(contract, address); + setDonations(userDonations); + setTotalDonated(userDonations.reduce((sum, donation) => sum + donation.amount, 0)); + } + }; + + return ( +
+

User Dashboard

+ +
+

Your Donations

+

Total Donated: {totalDonated} STX

+ + {donations.length === 0 ? ( +

You haven't made any donations yet.

+ ) : ( +
    + {donations.map((donation, index) => ( +
  • +

    {donation.beneficiaryName}

    +

    Amount: {donation.amount} STX

    +

    Date: {new Date(donation.timestamp * 1000).toLocaleDateString()}

    +
  • + ))} +
+ )} +
+
+ ); +}; + +export default UserDashboard; \ No newline at end of file diff --git a/donorfront/src/components/UtilizationList.js b/donorfront/src/components/UtilizationList.js index a6ddfaf..bd27ea9 100644 --- a/donorfront/src/components/UtilizationList.js +++ b/donorfront/src/components/UtilizationList.js @@ -19,4 +19,4 @@ const UtilizationList = ({ utilizations }) => { ); }; -export default UtilizationList; +export default UtilizationList; \ No newline at end of file diff --git a/donorfront/src/components/common/Button.js b/donorfront/src/components/common/Button.js new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/components/common/Input.js b/donorfront/src/components/common/Input.js new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/components/common/LoadingSpinner.js b/donorfront/src/components/common/LoadingSpinner.js new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/components/common/Modal.js b/donorfront/src/components/common/Modal.js new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/config.js b/donorfront/src/config.js new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/contexts/UserContext.js b/donorfront/src/contexts/UserContext.js new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/contexts/Web3Context.js b/donorfront/src/contexts/Web3Context.js new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/contracts/CharityContract.js b/donorfront/src/contracts/CharityContract.js new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/hooks/useAuth.js b/donorfront/src/hooks/useAuth.js new file mode 100644 index 0000000..22c6e15 --- /dev/null +++ b/donorfront/src/hooks/useAuth.js @@ -0,0 +1,41 @@ +import { useState, useEffect, useContext } from 'react'; +import { Web3Context } from '../contexts/Web3Context'; +import { UserContext } from '../contexts/UserContext'; +import { getContractInstance } from '../utils/contractHelpers'; + +export const useAuth = () => { + const { web3, account } = useContext(Web3Context); + const { user, setUser } = useContext(UserContext); + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [isAdmin, setIsAdmin] = useState(false); + + useEffect(() => { + const checkAuth = async () => { + if (web3 && account) { + const contract = getContractInstance(web3); + const roleData = await contract.methods.roles(account).call(); + + setIsAuthenticated(true); + setIsAdmin(roleData.role === '1'); // Assuming ROLE_ADMIN is represented by '1' + setUser({ address: account, role: roleData.role }); + } else { + setIsAuthenticated(false); + setIsAdmin(false); + setUser(null); + } + }; + + checkAuth(); + }, [web3, account, setUser]); + + const logout = () => { + // Implement logout logic here + setIsAuthenticated(false); + setIsAdmin(false); + setUser(null); + }; + + return { user, isAuthenticated, isAdmin, logout }; +}; + +export default useAuth; \ No newline at end of file diff --git a/donorfront/src/hooks/useSmartContract.js b/donorfront/src/hooks/useSmartContract.js new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/hooks/useWeb3.js b/donorfront/src/hooks/useWeb3.js new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/styles/components/AdminPanel.css b/donorfront/src/styles/components/AdminPanel.css new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/styles/components/BeneficiaryDetails.css b/donorfront/src/styles/components/BeneficiaryDetails.css new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/styles/components/BeneficiaryList.css b/donorfront/src/styles/components/BeneficiaryList.css new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/styles/components/DonationForm.css b/donorfront/src/styles/components/DonationForm.css new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/styles/components/Navigation.css b/donorfront/src/styles/components/Navigation.css new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/styles/components/UserDashboard.css b/donorfront/src/styles/components/UserDashboard.css new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/styles/components/UtilizationList.css b/donorfront/src/styles/components/UtilizationList.css new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/styles/global.css b/donorfront/src/styles/global.css new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/utils/contractHelpers.js b/donorfront/src/utils/contractHelpers.js new file mode 100644 index 0000000..0308445 --- /dev/null +++ b/donorfront/src/utils/contractHelpers.js @@ -0,0 +1,73 @@ +import { openContractCall } from '@stacks/connect'; +import { uintCV, stringUtf8CV } from '@stacks/transactions'; + +export const getBeneficiaries = async (contract) => { + // This is a placeholder. In a real app, you'd need to implement pagination + // and fetch beneficiaries from the contract storage + const beneficiaryCount = await contract.getBeneficiaryCount(); + const beneficiaries = []; + for (let i = 1; i <= beneficiaryCount; i++) { + const beneficiary = await contract.getBeneficiary(i); + beneficiaries.push(beneficiary); + } + return beneficiaries; +}; + +export const registerBeneficiary = async (contract, address, name, description, targetAmount) => { + const functionArgs = [ + stringUtf8CV(name), + stringUtf8CV(description), + uintCV(targetAmount) + ]; + + const options = { + contractAddress: contract.address, + contractName: contract.name, + functionName: 'register-beneficiary', + functionArgs, + senderAddress: address, + network: contract.network, + }; + + return openContractCall(options); +}; + +export const donate = async (contract, address, beneficiaryId, amount) => { + const functionArgs = [ + uintCV(beneficiaryId), + uintCV(amount) + ]; + + const options = { + contractAddress: contract.address, + contractName: contract.name, + functionName: 'donate', + functionArgs, + senderAddress: address, + network: contract.network, + }; + + return openContractCall(options); +}; + +export const approveUtilization = async (contract, address, beneficiaryId, milestoneId) => { + const functionArgs = [ + uintCV(beneficiaryId), + uintCV(milestoneId) + ]; + + const options = { + contractAddress: contract.address, + contractName: contract.name, + functionName: 'approve-utilization', + functionArgs, + senderAddress: address, + network: contract.network, + }; + + return openContractCall(options); +}; + +export const getUserDonations = async (contract, address) => { + return []; +}; \ No newline at end of file diff --git a/donorfront/src/utils/formatters.js b/donorfront/src/utils/formatters.js new file mode 100644 index 0000000..e69de29 diff --git a/donorfront/src/utils/validators.js b/donorfront/src/utils/validators.js new file mode 100644 index 0000000..e69de29