diff --git a/.DS_Store b/.DS_Store
index 8ef6025e..803fc4ad 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/client/client/pages/_app.jsx b/client/client/pages/_app.jsx
index 14e3a3de..6b6c1fc3 100644
--- a/client/client/pages/_app.jsx
+++ b/client/client/pages/_app.jsx
@@ -1,7 +1,8 @@
import '../styles/globals.css';
+import React from 'react';
// Imports for proposals/voting section
-import { ProposalDetails, CreateProposal, Home, Profile } from './proposals';
+import { ProposalDetails, CreateProposal, Home, Profile } from './proposals/Home';
function MyApp({ Component, pageProps }) {
return
diff --git a/client/client/pages/index.js b/client/client/pages/index.js
index f2365a14..dc4b6403 100644
--- a/client/client/pages/index.js
+++ b/client/client/pages/index.js
@@ -1,6 +1,6 @@
-import Head from 'next/head';
-import Image from 'next/image';
-import styles from '../styles/Home.module.css';
+import Head from 'next/head'
+import Image from 'next/image'
+import styles from '../styles/Home.module.css'
export default function Home() {
return (
diff --git a/client/client/pages/proposals/Home.jsx b/client/client/pages/proposals/Home.jsx
new file mode 100644
index 00000000..12c9a283
--- /dev/null
+++ b/client/client/pages/proposals/Home.jsx
@@ -0,0 +1,32 @@
+/*export { default as Home } from './Home';
+export { default as Profile } from './Profile';
+export { default as CreateProposal } from './CreateProposal';
+export { default as ProposalDetails } from './ProposalDetails';*/
+
+import React, { useState, useEffect } from "react";
+import { useStateContext } from './context';
+//import { DisplayProposals } from './components';
+
+const Home = () => {
+ const [isLoading, setIsLoading] = useState(false);
+ const [proposals, setProposals] = useState([]); // Empty array, retrieved from the state context from onchain
+
+ const { address, contract, getProposals } = useStateContext(); /*
+ const fetchProposals = async () => { // This is to allow us to call this g.request in the useEffect (as the request is async in /context)
+ setIsLoading(true);
+ const data = await getProposals();
+ setProposals(data);
+ setIsLoading(false);
+ }
+
+ useEffect(() => {
+ if (contract) fetchProposals();
+ console.log(proposals);
+ }, [address, contract]); // Re-called when these change*/
+
+ return (
+
Hello World
+ )
+}
+
+export default Home;
\ No newline at end of file
diff --git a/client/client/pages/proposals/assets/create-campaign.svg b/client/client/pages/proposals/assets/create-campaign.svg
new file mode 100644
index 00000000..d9c67303
--- /dev/null
+++ b/client/client/pages/proposals/assets/create-campaign.svg
@@ -0,0 +1,10 @@
+
diff --git a/client/client/pages/proposals/assets/dashboard.svg b/client/client/pages/proposals/assets/dashboard.svg
new file mode 100644
index 00000000..b9ecf4cf
--- /dev/null
+++ b/client/client/pages/proposals/assets/dashboard.svg
@@ -0,0 +1,6 @@
+
diff --git a/client/client/pages/proposals/assets/index.js b/client/client/pages/proposals/assets/index.js
new file mode 100644
index 00000000..49dff30b
--- /dev/null
+++ b/client/client/pages/proposals/assets/index.js
@@ -0,0 +1,31 @@
+import createCampaign from './create-campaign.svg';
+import dashboard from './dashboard.svg';
+import logo from './logo.svg';
+import logout from './logout.svg';
+import payment from './payment.svg';
+import profile from './profile.svg';
+import sun from './sun.svg';
+import withdraw from './withdraw.svg';
+import tagType from './type.svg';
+import search from './search.svg';
+import menu from './menu.svg';
+import money from './money.svg';
+import loader from './loader.svg';
+import thirdweb from './thirdweb.png';
+
+export {
+ tagType,
+ createCampaign,
+ dashboard,
+ logo,
+ logout,
+ payment,
+ profile,
+ sun,
+ withdraw,
+ search,
+ menu,
+ money,
+ loader,
+ thirdweb,
+};
diff --git a/client/client/pages/proposals/assets/loader.svg b/client/client/pages/proposals/assets/loader.svg
new file mode 100644
index 00000000..476b2dd4
--- /dev/null
+++ b/client/client/pages/proposals/assets/loader.svg
@@ -0,0 +1,51 @@
+
\ No newline at end of file
diff --git a/client/client/pages/proposals/assets/logo.svg b/client/client/pages/proposals/assets/logo.svg
new file mode 100644
index 00000000..ad1ca4ba
--- /dev/null
+++ b/client/client/pages/proposals/assets/logo.svg
@@ -0,0 +1,14 @@
+
diff --git a/client/client/pages/proposals/assets/logout.svg b/client/client/pages/proposals/assets/logout.svg
new file mode 100644
index 00000000..188cf3b3
--- /dev/null
+++ b/client/client/pages/proposals/assets/logout.svg
@@ -0,0 +1,4 @@
+
diff --git a/client/client/pages/proposals/assets/menu.svg b/client/client/pages/proposals/assets/menu.svg
new file mode 100644
index 00000000..4685dfbc
--- /dev/null
+++ b/client/client/pages/proposals/assets/menu.svg
@@ -0,0 +1,3 @@
+
diff --git a/client/client/pages/proposals/assets/money.svg b/client/client/pages/proposals/assets/money.svg
new file mode 100644
index 00000000..8bf7f8e5
--- /dev/null
+++ b/client/client/pages/proposals/assets/money.svg
@@ -0,0 +1,7 @@
+
diff --git a/client/client/pages/proposals/assets/payment.svg b/client/client/pages/proposals/assets/payment.svg
new file mode 100644
index 00000000..b0623289
--- /dev/null
+++ b/client/client/pages/proposals/assets/payment.svg
@@ -0,0 +1,3 @@
+
diff --git a/client/client/pages/proposals/assets/profile.svg b/client/client/pages/proposals/assets/profile.svg
new file mode 100644
index 00000000..0558003e
--- /dev/null
+++ b/client/client/pages/proposals/assets/profile.svg
@@ -0,0 +1,5 @@
+
diff --git a/client/client/pages/proposals/assets/search.svg b/client/client/pages/proposals/assets/search.svg
new file mode 100644
index 00000000..7155623f
--- /dev/null
+++ b/client/client/pages/proposals/assets/search.svg
@@ -0,0 +1,10 @@
+
diff --git a/client/client/pages/proposals/assets/sun.svg b/client/client/pages/proposals/assets/sun.svg
new file mode 100644
index 00000000..89ed57d6
--- /dev/null
+++ b/client/client/pages/proposals/assets/sun.svg
@@ -0,0 +1,3 @@
+
diff --git a/client/client/pages/proposals/assets/thirdweb.png b/client/client/pages/proposals/assets/thirdweb.png
new file mode 100644
index 00000000..2c64711f
Binary files /dev/null and b/client/client/pages/proposals/assets/thirdweb.png differ
diff --git a/client/client/pages/proposals/assets/type.svg b/client/client/pages/proposals/assets/type.svg
new file mode 100644
index 00000000..f8eac581
--- /dev/null
+++ b/client/client/pages/proposals/assets/type.svg
@@ -0,0 +1,3 @@
+
diff --git a/client/client/pages/proposals/assets/withdraw.svg b/client/client/pages/proposals/assets/withdraw.svg
new file mode 100644
index 00000000..29d8019b
--- /dev/null
+++ b/client/client/pages/proposals/assets/withdraw.svg
@@ -0,0 +1,12 @@
+
diff --git a/client/client/pages/proposals/components/DisplayProposals.jsx b/client/client/pages/proposals/components/DisplayProposals.jsx
new file mode 100644
index 00000000..09c40703
--- /dev/null
+++ b/client/client/pages/proposals/components/DisplayProposals.jsx
@@ -0,0 +1,38 @@
+import React from 'react';
+//import { useNavigate } from 'react-router-dom';
+//import FundCard from './FundCard';
+import { loader } from '../assets';
+
+const DisplayProposals = ({ title, isLoading, proposals }) => {
+ //const navigate = useNavigate();
+
+ const handleNavigate = (proposal) => {
+ navigate(`/proposal-details/${proposal.title}`, { state: proposal })
+ }
+
+ return (
+
+
{title} ({proposals.length})
+
+
+ {isLoading && (
+
+ )}
+
+ {!isLoading && proposals.length === 0 && (
+
+ You have not created any proposals yet
+
+ )}
+
+ {!isLoading && proposals.length > 0 && proposals.map((proposal) =>
handleNavigate(proposal)}
+ />)}
+
+
+ )
+}
+
+export default DisplayProposals;
\ No newline at end of file
diff --git a/client/client/pages/proposals/components/Sidebar.jsx b/client/client/pages/proposals/components/Sidebar.jsx
new file mode 100644
index 00000000..994c0436
--- /dev/null
+++ b/client/client/pages/proposals/components/Sidebar.jsx
@@ -0,0 +1,50 @@
+import React, { useState } from 'react';
+import { logo, sun } from '../assets';
+import { navlinks } from '../constants';
+//import { Link } from 'next';
+import { Link, useNavigate } from 'react-router-dom';
+
+
+const Icon = ({ styles, name, imgUrl, isActive, disabled, handleClick }) => (
+
+ {!isActive ? (
+
+ ) : (
+
+ )}
+
+);
+
+const Sidebar = () => {
+ const navigate = useNavigate();
+ const [isActive, setIsActive] = useState('dashboard');
+
+ return (
+
+
+
+
+
+
+
+ {navlinks.map((link) => (
+ {
+ if(!link.disabled) {
+ setIsActive(link.name);
+ navigate(link.link);
+ }
+ }}
+ />
+ ))}
+
+
+
+
+ );
+}
+
+export default Sidebar;
\ No newline at end of file
diff --git a/client/client/pages/proposals/constants/index.js b/client/client/pages/proposals/constants/index.js
new file mode 100644
index 00000000..555e82a3
--- /dev/null
+++ b/client/client/pages/proposals/constants/index.js
@@ -0,0 +1,37 @@
+import { createCampaign, dashboard, logout, payment, profile, withdraw } from '../assets';
+
+export const navlinks = [
+ {
+ name: 'dashboard',
+ imgUrl: dashboard,
+ link: '/',
+ },
+ {
+ name: 'campaign',
+ imgUrl: createCampaign,
+ link: '/create-proposal',
+ },
+ {
+ name: 'payment',
+ imgUrl: payment,
+ link: '/',
+ disabled: true,
+ },
+ {
+ name: 'withdraw',
+ imgUrl: withdraw,
+ link: '/',
+ disabled: true,
+ },
+ {
+ name: 'profile',
+ imgUrl: profile,
+ link: '/profile',
+ },
+ {
+ name: 'logout',
+ imgUrl: logout,
+ link: '/',
+ disabled: true,
+ },
+];
\ No newline at end of file
diff --git a/client/client/pages/proposals/context/index.jsx b/client/client/pages/proposals/context/index.jsx
new file mode 100644
index 00000000..71cc1c7e
--- /dev/null
+++ b/client/client/pages/proposals/context/index.jsx
@@ -0,0 +1,97 @@
+import React, { useContext, createContext } from 'react';
+import { useAddress, useContract, useMetamask, useContractWrite } from '@thirdweb-dev/react';
+import { ethers } from 'ethers';
+
+const StateContext = createContext();
+
+export const StateContextProvider = ({ children }) => {
+ const { contract } = useContract('0xCcaA1ABA77Bae6296D386C2F130c46FEc3E5A004');
+ const { mutateAsync: createProposal } = useContractWrite(contract, 'createProposal'); // Call function & create a proposal, passing in params from the form
+ const address = useAddress();
+ const connect = useMetamask();
+
+ // Publish a proposal on-chain
+ const publishProposal = async (form) => {
+ try {
+ const data = await createProposal([
+ address, // Owner - creator of the campaign. useMetamask();
+ form.title, // From CreateProposal.jsx
+ form.description,
+ form.target,
+ new Date(form.deadline).getTime(),
+ form.image,
+ ]);
+
+ console.log("Contract call success: ", data);
+ } catch (error) {
+ console.error('Contract call resulted in a failure, ', error);
+ }
+ }
+
+ // Retrieve proposals from on-chain
+ const getProposals = async () => {
+ const proposals = await contract.call('getProposals'); // Essentially a get request to the contract
+ const parsedProposals = proposals.map((proposal) => ({ // Take an individual proposal, immediate return
+ owner: proposal.owner,
+ title: proposal.title,
+ description: proposal.description,
+ target: ethers.utils.formatEther(proposal.target.toString()),
+ deadline: proposal.deadline.toNumber(), // Will transform to date format later
+ amountCollected: ethers.utils.formatEther(proposal.amountCollected.toString()),
+ image: proposal.image,
+ pId: proposal.i, // Index of proposal
+ }));
+
+ console.log(parsedProposals);
+ console.log(proposals);
+ return parsedProposals; // This is sent to the `useEffect` in `Home.jsx` page
+ }
+
+ const getUserProposals = async () => { // Get proposals that a specific user (authed) has created
+ const allProposals = await getProposals();
+ const filteredProposals = allProposals.filter((proposal) =>
+ proposal.owner === address
+ );
+ return filteredProposals;
+ }
+
+ const vote = async (pId, amount) => {
+ const data = await contract.call('voteForProposal', pId, { value: ethers.utils.parseEther(amount) });
+
+ return data;
+ }
+
+ const getVotes = async (pId) => {
+ const votes = await contract.call('getVoters', pId);
+ const numberOfVotes = votes[0].length;
+ const parsedVotes = [];
+
+ for (let i = 0; i < numberOfVotes; i++) {
+ parsedVotes.push({
+ donator: donations[0][i],
+ donation: ethers.utils.formatEther(donations[1][i].toString)
+ })
+ }
+
+ return parsedVotes;
+ }
+
+ return(
+
+ {children}
+
+ )
+}
+
+// Hook to get the context returned to node frontend
+export const useStateContext = () => useContext(StateContext);
\ No newline at end of file
diff --git a/client/client/pages/proposals/index.js b/client/client/pages/proposals/index.js
deleted file mode 100644
index eb9a1f19..00000000
--- a/client/client/pages/proposals/index.js
+++ /dev/null
@@ -1,4 +0,0 @@
-export { default as Home } from './Home';
-export { default as Profile } from './Profile';
-export { default as CreateProposal } from './CreateProposal';
-export { default as ProposalDetails } from './ProposalDetails';
\ No newline at end of file
diff --git a/client/client/pages/proposals/utils/index.js b/client/client/pages/proposals/utils/index.js
new file mode 100644
index 00000000..bf689a56
--- /dev/null
+++ b/client/client/pages/proposals/utils/index.js
@@ -0,0 +1,22 @@
+export const daysLeft = (deadline) => {
+ const difference = new Date(deadline).getTime() - Date.now();
+ const remainingDays = difference / (1000 * 3600 * 24);
+
+ return remainingDays.toFixed(0);
+ };
+
+ export const calculateBarPercentage = (goal, raisedAmount) => {
+ const percentage = Math.round((raisedAmount * 100) / goal);
+
+ return percentage;
+ };
+
+ export const checkIfImage = (url, callback) => {
+ const img = new Image();
+ img.src = url;
+
+ if (img.complete) callback(true);
+
+ img.onload = () => callback(true);
+ img.onerror = () => callback(false);
+ };
\ No newline at end of file
diff --git a/client/client/postcss.config.js b/client/client/postcss.config.js
new file mode 100644
index 00000000..56dcb488
--- /dev/null
+++ b/client/client/postcss.config.js
@@ -0,0 +1,7 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+ }
+
\ No newline at end of file
diff --git a/client/client/tailwind.config.js b/client/client/tailwind.config.js
new file mode 100644
index 00000000..16506015
--- /dev/null
+++ b/client/client/tailwind.config.js
@@ -0,0 +1,18 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: [
+ "./index.html",
+ "./src/**/*.{js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {
+ fontFamily: {
+ epilogue: ['Epilogue', 'sans-serif'],
+ },
+ boxShadow: {
+ secondary: '10px 10px 20px rgba(2, 2, 2, 0.25)',
+ },
+ },
+ },
+ plugins: [],
+ }
\ No newline at end of file
diff --git a/client/src/components/Content/Navbar.js b/client/src/components/Content/Navbar.js
new file mode 100644
index 00000000..73f380bf
--- /dev/null
+++ b/client/src/components/Content/Navbar.js
@@ -0,0 +1,9 @@
+import React from 'react';
+
+const Navbar = () => {
+ return (
+ Navbar
+ )
+}
+
+export default Navbar;
\ No newline at end of file
diff --git a/client/src/components/Content/README.md b/client/src/components/Content/README.md
new file mode 100644
index 00000000..34a41683
--- /dev/null
+++ b/client/src/components/Content/README.md
@@ -0,0 +1 @@
+Components relating to the Lens Protocol UI elements
\ No newline at end of file
diff --git a/client/src/components/DisplayProposals.jsx b/client/src/components/DisplayProposals.jsx
index 9dd78ad1..d9afb831 100644
--- a/client/src/components/DisplayProposals.jsx
+++ b/client/src/components/DisplayProposals.jsx
@@ -1,35 +1,38 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
-import { loader } from '../assets';
import FundCard from './FundCard';
+import { loader } from '../assets';
const DisplayProposals = ({ title, isLoading, proposals }) => {
- const navigate = useNavigate();
+ const navigate = useNavigate();
+
+ const handleNavigate = (proposal) => {
+ navigate(`/proposal-details/${proposal.title}`, { state: proposal })
+ }
+
+ return (
+
+
{title} ({proposals.length})
+
+
+ {isLoading && (
+
+ )}
- const handleNavigate = (proposal) => {
- navigate(`/proposal-details/${proposal.title}`, { state: proposal })
- }
+ {!isLoading && proposals.length === 0 && (
+
+ You have not created any proposals yet
+
+ )}
- return (
-
-
{title}: ({proposals.length})
-
- {isLoading && (
-
- )}
- {!isLoading && proposals.length === 0 && ( // If there are no proposals matching the query
-
- There are no proposals matching this query
-
- )}
- {!isLoading && proposals.length > 0 && proposals.map((proposal) =>
handleNavigate(proposal)}
- />)}
-
-
- )
+ {!isLoading && proposals.length > 0 && proposals.map((proposal) =>
handleNavigate(proposal)}
+ />)}
+
+
+ )
}
export default DisplayProposals;
\ No newline at end of file
diff --git a/client/src/context/index.jsx b/client/src/context/index.jsx
index acd11795..71cc1c7e 100644
--- a/client/src/context/index.jsx
+++ b/client/src/context/index.jsx
@@ -55,6 +55,27 @@ export const StateContextProvider = ({ children }) => {
return filteredProposals;
}
+ const vote = async (pId, amount) => {
+ const data = await contract.call('voteForProposal', pId, { value: ethers.utils.parseEther(amount) });
+
+ return data;
+ }
+
+ const getVotes = async (pId) => {
+ const votes = await contract.call('getVoters', pId);
+ const numberOfVotes = votes[0].length;
+ const parsedVotes = [];
+
+ for (let i = 0; i < numberOfVotes; i++) {
+ parsedVotes.push({
+ donator: donations[0][i],
+ donation: ethers.utils.formatEther(donations[1][i].toString)
+ })
+ }
+
+ return parsedVotes;
+ }
+
return(
{
createProposal: publishProposal,
getProposals,
getUserProposals,
+ vote,
+ getVotes,
}}
>
{children}
diff --git a/client/src/pages/ProposalDetails.jsx b/client/src/pages/ProposalDetails.jsx
index c0dfa1b3..e22adf15 100644
--- a/client/src/pages/ProposalDetails.jsx
+++ b/client/src/pages/ProposalDetails.jsx
@@ -8,7 +8,7 @@ import { thirdweb } from '../assets';
const ProposalDetails = () => {
const { state } = useLocation();
- const { getVoters, contract, address } = useStateContext();
+ const { vote, getVotes, contract, address } = useStateContext();
console.log(state);
const [isLoading, setIsLoading] = useState(false);
@@ -16,6 +16,24 @@ const ProposalDetails = () => {
const [voters, setVoters] = useState([]); // Array of voters on a specific proposal
const remainingDays = daysLeft(state.deadline);
+ const fetchVoters = async () => {
+ const data = await getVotes(state.pId);
+ setVoters(data);
+ }
+
+ useEffect(() => {
+ if(contract) fetchVoters();
+ }, [contract, address]);
+
+ const handleVote = async () => {
+ setIsLoading(true);
+
+ await vote(state.pId, amount);
+
+ navigate('/')
+ setIsLoading(false);
+ }
+
return (
{isLoading && 'Loading...'}
@@ -66,6 +84,31 @@ const ProposalDetails = () => {
+
+
Votes
+
+
Vote for this proposal
+
+
setAmount(e.target.value)}
+ />
+
+
Vote for this proposal with NO comments or adjustments
+
+
+
+
+
)
diff --git a/tsconfig.json b/tsconfig.json
index 84f59a7a..8aece21b 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -16,7 +16,8 @@
"module": "esnext",
"resolveJsonModule": true,
"isolatedModules": true,
- "jsx": "preserve"
+ "jsx": "preserve",
+ "moduleResolution": "node"
},
"include": [
"next-env.d.ts",