Skip to content

Commit e306172

Browse files
authored
Merge pull request #3 from ConstanceBeguier/seb
Seb
2 parents b8471fa + 44e7721 commit e306172

34 files changed

+24527
-0
lines changed

app/.eslintrc.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "next/core-web-vitals"
3+
}

app/.gitignore

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# next.js
12+
/.next/
13+
/out/
14+
15+
# production
16+
/build
17+
18+
# misc
19+
.DS_Store
20+
*.pem
21+
22+
# debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
.pnpm-debug.log*
27+
28+
# local env files
29+
.env*.local
30+
31+
# vercel
32+
.vercel
33+
34+
# typescript
35+
*.tsbuildinfo
36+
next-env.d.ts
37+
38+
# rust
39+
target

app/.yarnrc.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
nodeLinker: node-modules

app/components/BackButton.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
import { useRouter } from 'next/router';
3+
import style from '../styles/BackButton.module.css';
4+
5+
const BackButton = () => {
6+
const router = useRouter();
7+
8+
const goBack = () => {
9+
router.goBack(); // Utilise la pile d'historique de React Router pour revenir en arrière
10+
};
11+
12+
return (
13+
<button onClick={goBack} className={style.button}>
14+
Retour
15+
</button>
16+
);
17+
};
18+
19+
export default BackButton;

app/components/Header.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { WalletModalButton ,WalletDisconnectButton} from "@solana/wallet-adapter-react-ui";
2+
import { useAppContext } from "../context/context";
3+
import style from "../styles/Header.module.css";
4+
import Link from 'next/link';
5+
const Header = () => {
6+
const {isCo} = useAppContext();
7+
8+
return (
9+
<div className={style.wrapper}>
10+
<Link href="/">
11+
<div className={style.title}>PolliSol</div>
12+
</Link>
13+
<nav className={style.nav}>
14+
<Link href="/">
15+
<a>Accueil</a>
16+
</Link>
17+
{
18+
isCo &&<Link href="/proposal/create">
19+
<a>Create a Proposal</a>
20+
</Link>
21+
}
22+
</nav>
23+
{isCo ? <WalletDisconnectButton /> : <WalletModalButton/> }
24+
</div>
25+
);
26+
};
27+
28+
export default Header;

app/components/ResumeProposal.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { useEffect, useState } from "react";
2+
3+
import style from '../styles/ResumeProposal.module.css';
4+
import { toCamelCase } from "../utils/helper";
5+
const ResumeProposal = ({publicKey, account, setActiveClass}) => {
6+
const [period, setPeriod] = useState('');
7+
8+
useEffect(()=>{
9+
setPeriod(Object.values(account.period));
10+
setActiveClass(toCamelCase(Object.values(account.period)[0].toString()));
11+
},[account]);
12+
13+
return (
14+
<div className={style.card}>
15+
<div className={style.cardHeader}>
16+
<span className={style.cardTitle}>{account.title}</span>
17+
</div>
18+
<div className={style.cardBody}>
19+
<span className={style.cardPeriod}>Period : {period}</span>
20+
</div>
21+
<div className={style.cardFooter}>
22+
<span className={style.cardPubkey}>pubkey: {publicKey}</span>
23+
</div>
24+
</div>
25+
);
26+
}
27+
28+
export default ResumeProposal;

app/components/ViewProposal.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { useState } from "react";
2+
import { useAppContext } from "../context/context";
3+
import style from '../styles/ViewProposals.module.css';
4+
import ResumeProposal from "./ResumeProposal";
5+
import Link from 'next/link';
6+
const ViewProposals = () => {
7+
const {proposals} = useAppContext();
8+
const [activeClass, setActiveClass] = useState({}); // Un objet pour garder les états actifs des différents ResumeProposal
9+
10+
const handleSetActiveClass = (key, className) => {
11+
setActiveClass(prev => ({ ...prev, [key]: style[className] }));
12+
};
13+
14+
return (
15+
<div className={style.gridContainer}>
16+
{proposals?.map((proposal) => (
17+
<div key={proposal.publicKey} className={`${style.proposalContainer} ${activeClass[proposal.publicKey] || ''}`}>
18+
<Link href={`/proposal/${proposal.publicKey}`}>
19+
<a>
20+
<ResumeProposal
21+
key={proposal.publicKey}
22+
{...proposal}
23+
setActiveClass={className => handleSetActiveClass(proposal.publicKey, className)}
24+
/>
25+
</a>
26+
</Link>
27+
</div>
28+
))}
29+
</div>
30+
);
31+
};
32+
33+
export default ViewProposals;

app/context/context.js

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
import { createContext, useState, useEffect, useContext, useMemo } from "react";
2+
import { SystemProgram, Keypair, PublicKey } from "@solana/web3.js";
3+
import { useAnchorWallet, useConnection } from "@solana/wallet-adapter-react";
4+
import { BN } from "bn.js";
5+
6+
import {
7+
getProgram,
8+
getBallotAddress
9+
} from "../utils/program";
10+
import { confirmTx, mockWallet, stringToU8Array16, stringToU8Array32, u8ArrayToString } from "../utils/helper";
11+
12+
export const AppContext = createContext();
13+
14+
export const AppProvider = ({ children }) => {
15+
const [error, setError] = useState("");
16+
const [success, setSuccess] = useState("");
17+
const [isCo, setIsCo] = useState(false);
18+
const { connection } = useConnection();
19+
const wallet = useAnchorWallet();
20+
const program = useMemo(() => {
21+
if (connection) {
22+
return getProgram(connection, wallet ?? mockWallet());
23+
}
24+
}, [connection, wallet]);
25+
useEffect(() => {
26+
connection && wallet ? setIsCo(true) : setIsCo(false);
27+
}, [connection, wallet]);
28+
useEffect(() => {
29+
if(proposals.length == 0){
30+
fetch_proposals();
31+
}
32+
}, [program]);
33+
34+
const [proposals, setProposals] = useState([]);
35+
36+
const fetch_proposals = async () => {
37+
const proposals = await program.account.proposal.all();
38+
// const sortedVotes = proposals.sort((a, b) => a.account.deadline - b.account.deadline);
39+
const now = new Date().getTime();
40+
const readableProposals = proposals.map(proposal => {
41+
const tmpProposal = {
42+
publicKey: '',
43+
account: {
44+
admin: '',
45+
title: '',
46+
description: '',
47+
choices: [],
48+
choicesRegistrationInterval: {start:'', end:''},
49+
votersRegistrationInterval: {start:'', end:''},
50+
votingSessionInterval: {start:'', end:''},
51+
period: {},
52+
},
53+
};
54+
55+
tmpProposal.publicKey = proposal.publicKey.toString();
56+
tmpProposal.account.admin = proposal.account.admin.toString();
57+
tmpProposal.account.title = u8ArrayToString(proposal.account.title);
58+
tmpProposal.account.description = u8ArrayToString(proposal.account.description);
59+
tmpProposal.account.choices = (proposal.account.choices.length > 0 )
60+
? proposal.account.choices.map(ch=> { return { count: ch.count, label: u8ArrayToString(ch.label)}})
61+
: [];
62+
const choicesRegistrationIntervalStart = Number(proposal.account.choicesRegistrationInterval.start) * 1000;
63+
const choicesRegistrationIntervalEnd = Number(proposal.account.choicesRegistrationInterval.end) * 1000;
64+
const votersRegistrationIntervalStart = Number(proposal.account.votersRegistrationInterval.start) * 1000;
65+
const votersRegistrationIntervalEnd = Number(proposal.account.votersRegistrationInterval.end) * 1000;
66+
const votingSessionIntervalStart = Number(proposal.account.votingSessionInterval.start) * 1000;
67+
const votingSessionIntervalEnd = Number(proposal.account.votingSessionInterval.end) * 1000;
68+
69+
if(choicesRegistrationIntervalStart <= now && choicesRegistrationIntervalEnd >= now) {
70+
tmpProposal.account.period = {0: "Choices Registration"};
71+
} else if(votersRegistrationIntervalStart <= now && votersRegistrationIntervalEnd >= now) {
72+
tmpProposal.account.period = {1: "Voters Registration"};
73+
} else if(votingSessionIntervalStart <= now && votingSessionIntervalEnd >= now) {
74+
tmpProposal.account.period = {2: "Voting Session"};
75+
} else {
76+
tmpProposal.account.period = {3: "Terminate"};
77+
}
78+
tmpProposal.account.choicesRegistrationInterval.start = new Date(choicesRegistrationIntervalStart);
79+
tmpProposal.account.choicesRegistrationInterval.end = new Date(choicesRegistrationIntervalEnd);
80+
tmpProposal.account.votersRegistrationInterval.start = new Date(votersRegistrationIntervalStart);
81+
tmpProposal.account.votersRegistrationInterval.end = new Date(votersRegistrationIntervalEnd);
82+
tmpProposal.account.votingSessionInterval.start = new Date(votingSessionIntervalStart);
83+
tmpProposal.account.votingSessionInterval.end = new Date(votingSessionIntervalEnd);
84+
return tmpProposal;
85+
})
86+
setProposals(readableProposals);
87+
88+
}
89+
90+
const fetch_ballot = async (proposalPK) => {
91+
const ballotAddress = await getBallotAddress(new PublicKey(proposalPK), wallet.publicKey);
92+
const ballot = await program.account.ballot.fetch(ballotAddress);
93+
return ballot;
94+
}
95+
const create_proposal = async (
96+
title,
97+
description,
98+
cr_start,
99+
cr_end,
100+
vr_start,
101+
vr_end,
102+
vs_start,
103+
vs_end,
104+
) => {
105+
setError("");
106+
setSuccess("");
107+
try {
108+
const proposal = Keypair.generate();
109+
110+
const txHash = await program.methods
111+
.createProposal(
112+
stringToU8Array16(title),
113+
stringToU8Array32(description),
114+
new BN(cr_start),
115+
new BN(cr_end),
116+
new BN(vr_start),
117+
new BN(vr_end),
118+
new BN(vs_start),
119+
new BN(vs_end),
120+
)
121+
.accounts({
122+
proposal: proposal.publicKey,
123+
admin: wallet.publicKey,
124+
systemProgram: SystemProgram.programId,
125+
})
126+
.signers([proposal])
127+
.rpc();
128+
129+
const confirm = await confirmTx(txHash, connection);
130+
131+
await fetch_proposals();
132+
if(confirm) {
133+
setSuccess('Proposal Create');
134+
const newProposal = proposals.find(pp=>pp.account.title == title && pp.account.description == description);
135+
return newProposal;
136+
}
137+
} catch (err) {
138+
setError(err.message.split('Error Message:')[1]);
139+
}
140+
};
141+
142+
const add_choice_for_one_proposal = async (choice, proposalPK) => {
143+
setError("");
144+
setSuccess("");
145+
try {
146+
147+
const txHash = await program.methods
148+
.addChoiceForOneProposal(
149+
stringToU8Array16(choice),
150+
)
151+
.accounts({
152+
proposal: proposalPK,
153+
admin: wallet.publicKey,
154+
})
155+
.signers([])
156+
.rpc();
157+
await confirmTx(txHash, connection);
158+
159+
fetch_proposals();
160+
} catch (err) {
161+
setError(err.message.split('Error Message:')[1]);
162+
}
163+
};
164+
const register_voter = async (voter, proposalPK) => {
165+
setError("");
166+
setSuccess("");
167+
try {
168+
const ballotAddress = await getBallotAddress(new PublicKey(proposalPK), new PublicKey(voter));
169+
170+
const txHash = await program.methods
171+
.registerVoter(new PublicKey(voter))
172+
.accounts({
173+
proposal: proposalPK,
174+
ballot: ballotAddress,
175+
admin: wallet.publicKey,
176+
systemProgram: SystemProgram.programId,
177+
})
178+
.signers([])
179+
.rpc();
180+
await confirmTx(txHash, connection);
181+
182+
fetch_proposals();
183+
} catch (err) {
184+
setError(err.message.split('Error Message:')[1]);
185+
}
186+
};
187+
const cast_vote = async (index, proposalPK) => {
188+
try {
189+
const ballotAddress = await getBallotAddress(new PublicKey(proposalPK), wallet.publicKey);
190+
191+
const txHash = await program.methods
192+
.castVote(index)
193+
.accounts({
194+
proposal: proposalPK,
195+
ballot: ballotAddress,
196+
voter: wallet.publicKey,
197+
})
198+
.signers([])
199+
.rpc();
200+
await confirmTx(txHash, connection);
201+
202+
fetch_proposals();
203+
} catch (err) {
204+
console.log("err", err);
205+
setError(err.message);
206+
}
207+
}
208+
return (
209+
<AppContext.Provider
210+
value={{
211+
create_proposal,
212+
fetch_proposals,
213+
fetch_ballot,
214+
cast_vote,
215+
register_voter,
216+
add_choice_for_one_proposal,
217+
proposals,
218+
error,
219+
success,
220+
isCo
221+
}}
222+
>
223+
{children}
224+
</AppContext.Provider>
225+
);
226+
};
227+
228+
export const useAppContext = () => {
229+
return useContext(AppContext);
230+
};

app/next.config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/** @type {import('next').NextConfig} */
2+
const nextConfig = {
3+
reactStrictMode: true,
4+
swcMinify: true,
5+
}
6+
7+
module.exports = nextConfig

0 commit comments

Comments
 (0)