Skip to content

Commit

Permalink
Merge pull request #3 from ConstanceBeguier/seb
Browse files Browse the repository at this point in the history
Seb
  • Loading branch information
manthis authored Jul 22, 2024
2 parents b8471fa + 44e7721 commit e306172
Show file tree
Hide file tree
Showing 34 changed files with 24,527 additions and 0 deletions.
3 changes: 3 additions & 0 deletions app/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
39 changes: 39 additions & 0 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

# rust
target
1 change: 1 addition & 0 deletions app/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
19 changes: 19 additions & 0 deletions app/components/BackButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react';
import { useRouter } from 'next/router';
import style from '../styles/BackButton.module.css';

const BackButton = () => {
const router = useRouter();

const goBack = () => {
router.goBack(); // Utilise la pile d'historique de React Router pour revenir en arrière
};

return (
<button onClick={goBack} className={style.button}>
Retour
</button>
);
};

export default BackButton;
28 changes: 28 additions & 0 deletions app/components/Header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { WalletModalButton ,WalletDisconnectButton} from "@solana/wallet-adapter-react-ui";
import { useAppContext } from "../context/context";
import style from "../styles/Header.module.css";
import Link from 'next/link';
const Header = () => {
const {isCo} = useAppContext();

return (
<div className={style.wrapper}>
<Link href="/">
<div className={style.title}>PolliSol</div>
</Link>
<nav className={style.nav}>
<Link href="/">
<a>Accueil</a>
</Link>
{
isCo &&<Link href="/proposal/create">
<a>Create a Proposal</a>
</Link>
}
</nav>
{isCo ? <WalletDisconnectButton /> : <WalletModalButton/> }
</div>
);
};

export default Header;
28 changes: 28 additions & 0 deletions app/components/ResumeProposal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useEffect, useState } from "react";

import style from '../styles/ResumeProposal.module.css';
import { toCamelCase } from "../utils/helper";
const ResumeProposal = ({publicKey, account, setActiveClass}) => {
const [period, setPeriod] = useState('');

useEffect(()=>{
setPeriod(Object.values(account.period));
setActiveClass(toCamelCase(Object.values(account.period)[0].toString()));
},[account]);

return (
<div className={style.card}>
<div className={style.cardHeader}>
<span className={style.cardTitle}>{account.title}</span>
</div>
<div className={style.cardBody}>
<span className={style.cardPeriod}>Period : {period}</span>
</div>
<div className={style.cardFooter}>
<span className={style.cardPubkey}>pubkey: {publicKey}</span>
</div>
</div>
);
}

export default ResumeProposal;
33 changes: 33 additions & 0 deletions app/components/ViewProposal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useState } from "react";
import { useAppContext } from "../context/context";
import style from '../styles/ViewProposals.module.css';
import ResumeProposal from "./ResumeProposal";
import Link from 'next/link';
const ViewProposals = () => {
const {proposals} = useAppContext();
const [activeClass, setActiveClass] = useState({}); // Un objet pour garder les états actifs des différents ResumeProposal

const handleSetActiveClass = (key, className) => {
setActiveClass(prev => ({ ...prev, [key]: style[className] }));
};

return (
<div className={style.gridContainer}>
{proposals?.map((proposal) => (
<div key={proposal.publicKey} className={`${style.proposalContainer} ${activeClass[proposal.publicKey] || ''}`}>
<Link href={`/proposal/${proposal.publicKey}`}>
<a>
<ResumeProposal
key={proposal.publicKey}
{...proposal}
setActiveClass={className => handleSetActiveClass(proposal.publicKey, className)}
/>
</a>
</Link>
</div>
))}
</div>
);
};

export default ViewProposals;
230 changes: 230 additions & 0 deletions app/context/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
import { createContext, useState, useEffect, useContext, useMemo } from "react";
import { SystemProgram, Keypair, PublicKey } from "@solana/web3.js";
import { useAnchorWallet, useConnection } from "@solana/wallet-adapter-react";
import { BN } from "bn.js";

import {
getProgram,
getBallotAddress
} from "../utils/program";
import { confirmTx, mockWallet, stringToU8Array16, stringToU8Array32, u8ArrayToString } from "../utils/helper";

export const AppContext = createContext();

export const AppProvider = ({ children }) => {
const [error, setError] = useState("");
const [success, setSuccess] = useState("");
const [isCo, setIsCo] = useState(false);
const { connection } = useConnection();
const wallet = useAnchorWallet();
const program = useMemo(() => {
if (connection) {
return getProgram(connection, wallet ?? mockWallet());
}
}, [connection, wallet]);
useEffect(() => {
connection && wallet ? setIsCo(true) : setIsCo(false);
}, [connection, wallet]);
useEffect(() => {
if(proposals.length == 0){
fetch_proposals();
}
}, [program]);

const [proposals, setProposals] = useState([]);

const fetch_proposals = async () => {
const proposals = await program.account.proposal.all();
// const sortedVotes = proposals.sort((a, b) => a.account.deadline - b.account.deadline);
const now = new Date().getTime();
const readableProposals = proposals.map(proposal => {
const tmpProposal = {
publicKey: '',
account: {
admin: '',
title: '',
description: '',
choices: [],
choicesRegistrationInterval: {start:'', end:''},
votersRegistrationInterval: {start:'', end:''},
votingSessionInterval: {start:'', end:''},
period: {},
},
};

tmpProposal.publicKey = proposal.publicKey.toString();
tmpProposal.account.admin = proposal.account.admin.toString();
tmpProposal.account.title = u8ArrayToString(proposal.account.title);
tmpProposal.account.description = u8ArrayToString(proposal.account.description);
tmpProposal.account.choices = (proposal.account.choices.length > 0 )
? proposal.account.choices.map(ch=> { return { count: ch.count, label: u8ArrayToString(ch.label)}})
: [];
const choicesRegistrationIntervalStart = Number(proposal.account.choicesRegistrationInterval.start) * 1000;
const choicesRegistrationIntervalEnd = Number(proposal.account.choicesRegistrationInterval.end) * 1000;
const votersRegistrationIntervalStart = Number(proposal.account.votersRegistrationInterval.start) * 1000;
const votersRegistrationIntervalEnd = Number(proposal.account.votersRegistrationInterval.end) * 1000;
const votingSessionIntervalStart = Number(proposal.account.votingSessionInterval.start) * 1000;
const votingSessionIntervalEnd = Number(proposal.account.votingSessionInterval.end) * 1000;

if(choicesRegistrationIntervalStart <= now && choicesRegistrationIntervalEnd >= now) {
tmpProposal.account.period = {0: "Choices Registration"};
} else if(votersRegistrationIntervalStart <= now && votersRegistrationIntervalEnd >= now) {
tmpProposal.account.period = {1: "Voters Registration"};
} else if(votingSessionIntervalStart <= now && votingSessionIntervalEnd >= now) {
tmpProposal.account.period = {2: "Voting Session"};
} else {
tmpProposal.account.period = {3: "Terminate"};
}
tmpProposal.account.choicesRegistrationInterval.start = new Date(choicesRegistrationIntervalStart);
tmpProposal.account.choicesRegistrationInterval.end = new Date(choicesRegistrationIntervalEnd);
tmpProposal.account.votersRegistrationInterval.start = new Date(votersRegistrationIntervalStart);
tmpProposal.account.votersRegistrationInterval.end = new Date(votersRegistrationIntervalEnd);
tmpProposal.account.votingSessionInterval.start = new Date(votingSessionIntervalStart);
tmpProposal.account.votingSessionInterval.end = new Date(votingSessionIntervalEnd);
return tmpProposal;
})
setProposals(readableProposals);

}

const fetch_ballot = async (proposalPK) => {
const ballotAddress = await getBallotAddress(new PublicKey(proposalPK), wallet.publicKey);
const ballot = await program.account.ballot.fetch(ballotAddress);
return ballot;
}
const create_proposal = async (
title,
description,
cr_start,
cr_end,
vr_start,
vr_end,
vs_start,
vs_end,
) => {
setError("");
setSuccess("");
try {
const proposal = Keypair.generate();

const txHash = await program.methods
.createProposal(
stringToU8Array16(title),
stringToU8Array32(description),
new BN(cr_start),
new BN(cr_end),
new BN(vr_start),
new BN(vr_end),
new BN(vs_start),
new BN(vs_end),
)
.accounts({
proposal: proposal.publicKey,
admin: wallet.publicKey,
systemProgram: SystemProgram.programId,
})
.signers([proposal])
.rpc();

const confirm = await confirmTx(txHash, connection);

await fetch_proposals();
if(confirm) {
setSuccess('Proposal Create');
const newProposal = proposals.find(pp=>pp.account.title == title && pp.account.description == description);
return newProposal;
}
} catch (err) {
setError(err.message.split('Error Message:')[1]);
}
};

const add_choice_for_one_proposal = async (choice, proposalPK) => {
setError("");
setSuccess("");
try {

const txHash = await program.methods
.addChoiceForOneProposal(
stringToU8Array16(choice),
)
.accounts({
proposal: proposalPK,
admin: wallet.publicKey,
})
.signers([])
.rpc();
await confirmTx(txHash, connection);

fetch_proposals();
} catch (err) {
setError(err.message.split('Error Message:')[1]);
}
};
const register_voter = async (voter, proposalPK) => {
setError("");
setSuccess("");
try {
const ballotAddress = await getBallotAddress(new PublicKey(proposalPK), new PublicKey(voter));

const txHash = await program.methods
.registerVoter(new PublicKey(voter))
.accounts({
proposal: proposalPK,
ballot: ballotAddress,
admin: wallet.publicKey,
systemProgram: SystemProgram.programId,
})
.signers([])
.rpc();
await confirmTx(txHash, connection);

fetch_proposals();
} catch (err) {
setError(err.message.split('Error Message:')[1]);
}
};
const cast_vote = async (index, proposalPK) => {
try {
const ballotAddress = await getBallotAddress(new PublicKey(proposalPK), wallet.publicKey);

const txHash = await program.methods
.castVote(index)
.accounts({
proposal: proposalPK,
ballot: ballotAddress,
voter: wallet.publicKey,
})
.signers([])
.rpc();
await confirmTx(txHash, connection);

fetch_proposals();
} catch (err) {
console.log("err", err);
setError(err.message);
}
}
return (
<AppContext.Provider
value={{
create_proposal,
fetch_proposals,
fetch_ballot,
cast_vote,
register_voter,
add_choice_for_one_proposal,
proposals,
error,
success,
isCo
}}
>
{children}
</AppContext.Provider>
);
};

export const useAppContext = () => {
return useContext(AppContext);
};
7 changes: 7 additions & 0 deletions app/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
}

module.exports = nextConfig
Loading

0 comments on commit e306172

Please sign in to comment.