From 64bf705278e94322bee8d4fcfa873f7557e05743 Mon Sep 17 00:00:00 2001 From: yusei399 Date: Sun, 16 Apr 2023 02:15:35 +0900 Subject: [PATCH 1/5] votingList --- ui/src/pages/voteList.page.tsx | 92 ++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 ui/src/pages/voteList.page.tsx diff --git a/ui/src/pages/voteList.page.tsx b/ui/src/pages/voteList.page.tsx new file mode 100644 index 0000000..fa079ae --- /dev/null +++ b/ui/src/pages/voteList.page.tsx @@ -0,0 +1,92 @@ +import { Box, Button, ChakraProvider, Text } from "@chakra-ui/react"; +import { useSelector, useDispatch } from "react-redux"; +import { RootState, addVote, voteContent } from "../redux/store"; +import { useState } from "react"; +import Link from "next/link"; + +const VoteList = () => { + const votes = useSelector((state: RootState) => state.votes); + const dispatch = useDispatch(); + const [voteDisplayStates, setVoteDisplayStates] = useState( + Array(votes.length).fill(false) + ); + + const handleDelete = (voteIndex: number) => { + const newVotes = [...votes]; + newVotes.splice(voteIndex, 1); + dispatch({ type: 'votes/deleteVote', payload: newVotes }); + } + + const hasExpired = (closedAt: string) => { + return new Date(closedAt).getTime() < Date.now(); + }; + + const handleVote = (voteIndex: number, contentIndex: number) => { + const vote = votes[voteIndex]; + const now = new Date().toISOString(); + if (vote.closingTime && now > vote.closingTime) { + // 締切時間を過ぎているため、投票できません + return; + } + dispatch(voteContent({ voteIndex, contentIndex })); + }; + + if (!votes || votes.length === 0) { + return ( + + 投票がありません。 + ホームに戻る + ); + } + + console.log(votes); + return ( + +

投票一覧

+ {votes.map((vote, voteIndex) => ( + + + {vote.title} + + + {vote.contents && + vote.contents.map((content, contentIndex) => { + const count = vote.count && vote.count[contentIndex]; + const buttonText = voteDisplayStates[voteIndex] + ? `${content} (${count || 0})` + : content; + const disabled = vote.closingTime && hasExpired(vote.closingTime); + return ( + + ); + })} + + + + + ))} + 戻る +
+ ); +}; + +export default VoteList; + + From e92b8fda66f5cd4839fb7abb151168f2908e1e13 Mon Sep 17 00:00:00 2001 From: yusei399 Date: Sun, 16 Apr 2023 02:16:47 +0900 Subject: [PATCH 2/5] redux&&vote --- ui/src/pages/voting.page.tsx | 17 ++++++++ ui/src/redux/store.tsx | 82 ++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 ui/src/pages/voting.page.tsx create mode 100644 ui/src/redux/store.tsx diff --git a/ui/src/pages/voting.page.tsx b/ui/src/pages/voting.page.tsx new file mode 100644 index 0000000..8d6ab87 --- /dev/null +++ b/ui/src/pages/voting.page.tsx @@ -0,0 +1,17 @@ +import { Card } from "@chakra-ui/react"; +import React from "react"; +import VoteList from "./voteList.page"; + +const Voting = () => { + return ( +
+

投票ページ

+ + + +
+ ); +}; + +export default Voting; + diff --git a/ui/src/redux/store.tsx b/ui/src/redux/store.tsx new file mode 100644 index 0000000..1c2bb9f --- /dev/null +++ b/ui/src/redux/store.tsx @@ -0,0 +1,82 @@ +import { configureStore, createSlice, PayloadAction } from "@reduxjs/toolkit"; + +interface Vote { + title: string; + contents: string[]; + count: number[]; + closedAt?: string; + closingTime?: string; +} + +interface RootState { + votes: Vote[]; +} + +const initialState: RootState = { + votes: [] +}; + +const votesSlice = createSlice({ + name: 'votes', + initialState, + reducers: { + addVote: (state, action: PayloadAction) => { + const vote = action.payload; + vote.count = new Array(vote.contents.length).fill(0); + state.votes.push(vote); + }, + voteContent: (state, action: PayloadAction<{ voteIndex: number, contentIndex: number }>) => { + const { voteIndex, contentIndex } = action.payload; + const vote = state.votes[voteIndex]; + vote.count[contentIndex]++; + }, + deleteVote: (state, action: PayloadAction) => { + state.votes.splice(action.payload, 1); + }, + closeVote: (state, action: PayloadAction) => { + const vote = state.votes[action.payload]; + vote.closedAt = new Date().toISOString(); + } + } +}); + +const saveState = (state: RootState) => { + try { + const serializedState = JSON.stringify(state); + localStorage.setItem("state", serializedState); + } catch (err) { + console.log(err); + } +}; + +const loadState = (): RootState | undefined => { + try { + const serializedState = localStorage.getItem("state"); + if (serializedState === null) { + return undefined; + } + return JSON.parse(serializedState); + } catch (err) { + console.log(err); + return undefined; + } +}; + +const persistedState = loadState(); + +const store = configureStore({ + reducer: votesSlice.reducer, + preloadedState: persistedState, +}); + +store.subscribe(() => { + saveState(store.getState()); +}); + +export type { RootState }; +export const { addVote, voteContent, deleteVote, closeVote } = votesSlice.actions; + +export default store; + + + From 9a768a77f05ec641ea4059caa12a11c7849a9e0f Mon Sep 17 00:00:00 2001 From: yusei399 Date: Sun, 16 Apr 2023 03:31:48 +0900 Subject: [PATCH 3/5] =?UTF-8?q?build=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E5=9E=8B=E3=81=AA=E3=81=A9=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/src/pages/createvote.page.tsx | 2 +- ui/src/pages/voteList.page.tsx | 2 +- ui/src/redux/store.tsx | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/ui/src/pages/createvote.page.tsx b/ui/src/pages/createvote.page.tsx index 59ae0de..da4dd06 100644 --- a/ui/src/pages/createvote.page.tsx +++ b/ui/src/pages/createvote.page.tsx @@ -28,7 +28,7 @@ const CreateVote = () => { const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); - const vote = { title, contents, closingTime }; + const vote = { title, contents, closingTime}; dispatch(addVote(vote)); setTitle(""); setContents([]); diff --git a/ui/src/pages/voteList.page.tsx b/ui/src/pages/voteList.page.tsx index fa079ae..cfa9d99 100644 --- a/ui/src/pages/voteList.page.tsx +++ b/ui/src/pages/voteList.page.tsx @@ -62,7 +62,7 @@ const VoteList = () => { mr="2" mb="2" onClick={() => handleVote(voteIndex, contentIndex)} - isDisabled={disabled} + isDisabled={Boolean(disabled)} > {buttonText} {vote.closingTime && hasExpired(vote.closingTime) ? "(締切)" : ""} diff --git a/ui/src/redux/store.tsx b/ui/src/redux/store.tsx index 1c2bb9f..7977678 100644 --- a/ui/src/redux/store.tsx +++ b/ui/src/redux/store.tsx @@ -20,10 +20,12 @@ const votesSlice = createSlice({ name: 'votes', initialState, reducers: { - addVote: (state, action: PayloadAction) => { + addVote: (state, action: PayloadAction>) => { const vote = action.payload; - vote.count = new Array(vote.contents.length).fill(0); - state.votes.push(vote); + const contentsLength = vote.contents.length; + const count = new Array(contentsLength).fill(0); + const voteWithCount = {...vote, count}; + state.votes.push(voteWithCount); }, voteContent: (state, action: PayloadAction<{ voteIndex: number, contentIndex: number }>) => { const { voteIndex, contentIndex } = action.payload; From 9945ff99bd044aa457af5f6b11d8f5c77e5fffc4 Mon Sep 17 00:00:00 2001 From: yusei399 Date: Sun, 16 Apr 2023 04:38:48 +0900 Subject: [PATCH 4/5] =?UTF-8?q?css=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/src/pages/createvote.page.tsx | 17 ++-- ui/src/pages/index.page.tsx | 83 ++++++++++++---- ui/src/styles/Home.module.css | 163 +++++++++---------------------- 3 files changed, 119 insertions(+), 144 deletions(-) diff --git a/ui/src/pages/createvote.page.tsx b/ui/src/pages/createvote.page.tsx index da4dd06..8ec6a5f 100644 --- a/ui/src/pages/createvote.page.tsx +++ b/ui/src/pages/createvote.page.tsx @@ -1,7 +1,9 @@ import { Button, ChakraProvider, Input, Link } from "@chakra-ui/react"; +import { statSync } from "fs"; import { useState } from "react"; import { useDispatch } from "react-redux"; import { addVote } from "../redux/store"; +import styles from '../styles/Home.module.css'; const CreateVote = () => { const [title, setTitle] = useState(""); @@ -53,25 +55,27 @@ const CreateVote = () => {

投票作成

-
- +
+
{contents.map((content, index) => ( -
- +
+ handleContentChange(event, index)} required + width={200} />
@@ -79,7 +83,7 @@ const CreateVote = () => { -
+
{ value={closingTime} onChange={handleClosingTimeChange} required + width={200} />
- + diff --git a/ui/src/pages/index.page.tsx b/ui/src/pages/index.page.tsx index 9ae4518..23955b5 100644 --- a/ui/src/pages/index.page.tsx +++ b/ui/src/pages/index.page.tsx @@ -1,10 +1,7 @@ - import Head from 'next/head'; import Image from 'next/image'; import styles from '../styles/Home.module.css'; import { useEffect, useState } from 'react'; -import type { Add } from '../../../contracts/src/'; -import CreateVote from './createvote.page'; import { Mina, isReady, @@ -12,31 +9,77 @@ import { fetchAccount, } from 'snarkyjs'; import { - FormErrorMessage, - FormLabel, - FormControl, - Input, Button, ChakraProvider, } from '@chakra-ui/react' -import { theme } from '@chakra-ui/react'; import Link from 'next/link'; -import VoteList from "./voteList.page"; - +import Web3 from 'web3'; +declare global { + interface Window { + auro: any; + } +} export default function Home() { - return( - <> - - - - - - - + const [web3, setWeb3] = useState(); + + useEffect(() => { + async function connectWallet() { + if (typeof window !== 'undefined' && typeof window.auro !== 'undefined') { + const provider = await window.auro.enable(); + const web3Instance = new Web3(provider); + setWeb3(web3Instance); + } else { + console.error('Auro Wallet provider not found'); + } + } + connectWallet(); + }, []); + + return ( +
+ +

Mina Protocol Voting App

+ + + +
+ + + + + + +
- +
); }; + + + + + + + + +// const [web3, setWeb3] = useState(null); + +// useEffect(() => { +// const initWeb3 = async () => { +// if (typeof window.ethereum !== 'undefined') { +// // auroWalletがインストールされているか確認する +// const provider = window.ethereum; +// // Web3オブジェクトを作成し、Ethereumブロックチェーンに接続する +// const web3 = new Web3(provider); +// // auroWalletに接続する +// await provider.request({ method: 'eth_requestAccounts' }); +// setWeb3(web3); +// } else { +// console.log('auroWalletがインストールされていません'); +// } +// }; +// initWeb3(); +// }, []); \ No newline at end of file diff --git a/ui/src/styles/Home.module.css b/ui/src/styles/Home.module.css index a3adb16..b6e2b71 100644 --- a/ui/src/styles/Home.module.css +++ b/ui/src/styles/Home.module.css @@ -108,122 +108,49 @@ position: relative; } /* Enable hover only on non-touch devices */ -@media (hover: hover) and (pointer: fine) { - .card:hover { - background: rgba(var(--card-rgb), 0.1); - border: 1px solid rgba(var(--card-border-rgb), 0.15); - } - - .card:hover span { - transform: translateX(4px); - } -} - -@media (prefers-reduced-motion) { - .card:hover span { - transform: none; - } -} - -/* Mobile */ -@media (max-width: 700px) { - .content { - padding: 4rem; - } - - .grid { - grid-template-columns: 1fr; - margin-bottom: 120px; - max-width: 320px; - text-align: center; - } - - .card { - padding: 1rem 2.5rem; - } - - .card h2 { - margin-bottom: 0.5rem; - } - - .center { - padding: 8rem 0 6rem; - } - - .center::before { - transform: none; - height: 300px; - } - - .description { - font-size: 0.8rem; - } - - .description a { - padding: 1rem; - } - - .description p, - .description div { - display: flex; - justify-content: center; - position: fixed; - width: 100%; - } - - .description p { - align-items: center; - inset: 0 0 auto; - padding: 2rem 1rem 1.4rem; - border-radius: 0; - border: none; - border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); - background: linear-gradient( - to bottom, - rgba(var(--background-start-rgb), 1), - rgba(var(--callout-rgb), 0.5) - ); - background-clip: padding-box; - backdrop-filter: blur(24px); - } - - .description div { - align-items: flex-end; - pointer-events: none; - inset: auto 0 0; - padding: 2rem; - height: 200px; - background: linear-gradient( - to bottom, - transparent 0%, - rgb(var(--background-end-rgb)) 40% - ); - z-index: 1; - } -} - -/* Tablet and Smaller Desktop */ -@media (min-width: 701px) and (max-width: 1120px) { - .grid { - grid-template-columns: repeat(2, 50%); - } -} - -@media (prefers-color-scheme: dark) { - .vercelLogo { - filter: invert(1); - } - - .logo { - filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); - } -} - -@keyframes rotate { - from { - transform: rotate(360deg); - } - to { - transform: rotate(0deg); - } + + +.head { + color: #6c63ff; + font-size: 2.5rem; + font-weight: bold; + text-align: center; + text-shadow: 2px 2px #f0f0f0; + margin-bottom: 1.5rem; + margin-right: 20%; + margin-top: 10%; +} + +/* .background { + background-color: #6c63ff; } */ + +.container { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 0 2rem; + max-width: var(--max-width); + width: 80%; + z-index: 2; +} + +.create{ + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + padding: 0 2rem; + max-width: var(--max-width); + width: 80%; + z-index: 2; +} + + +.voteTitle{ + font-size: 1.5rem; + font-weight: 600; + margin-bottom: 0.7rem; +} + From 03521ec438f474eea0b3360fa13fa69e6de6421c Mon Sep 17 00:00:00 2001 From: yusei399 Date: Sun, 16 Apr 2023 05:08:18 +0900 Subject: [PATCH 5/5] =?UTF-8?q?submit=E3=81=97=E3=81=9F=E9=9A=9B=E3=81=AE?= =?UTF-8?q?=E3=83=AA=E3=83=80=E3=82=A4=E3=83=AC=E3=82=AF=E3=83=88=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=81=AA=E3=81=84=E3=83=90=E3=82=B0=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/src/pages/createvote.page.tsx | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/ui/src/pages/createvote.page.tsx b/ui/src/pages/createvote.page.tsx index 8ec6a5f..6a59136 100644 --- a/ui/src/pages/createvote.page.tsx +++ b/ui/src/pages/createvote.page.tsx @@ -1,9 +1,7 @@ import { Button, ChakraProvider, Input, Link } from "@chakra-ui/react"; -import { statSync } from "fs"; import { useState } from "react"; import { useDispatch } from "react-redux"; import { addVote } from "../redux/store"; -import styles from '../styles/Home.module.css'; const CreateVote = () => { const [title, setTitle] = useState(""); @@ -30,7 +28,7 @@ const CreateVote = () => { const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); - const vote = { title, contents, closingTime}; + const vote = { title, contents, closingTime }; dispatch(addVote(vote)); setTitle(""); setContents([]); @@ -55,27 +53,25 @@ const CreateVote = () => {

投票作成

-
- +
+
{contents.map((content, index) => ( -
- +
+ handleContentChange(event, index)} required - width={200} />
@@ -83,7 +79,7 @@ const CreateVote = () => { -
+
{ value={closingTime} onChange={handleClosingTimeChange} required - width={200} />
- + @@ -108,3 +103,4 @@ export default CreateVote; +