diff --git a/app/node_modules/.package-lock.json b/app/node_modules/.package-lock.json index 50e8428f..4a849eb8 100644 --- a/app/node_modules/.package-lock.json +++ b/app/node_modules/.package-lock.json @@ -3896,6 +3896,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==" }, + "node_modules/@types/papaparse": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.8.tgz", + "integrity": "sha512-ArKIEOOWULbhi53wkAiRy1ze4wvrTfhpAj7Yfzva+EkmX2sV8PpFB+xqzJfzXNzK4me95FJH9QZt5NXFVGzOoQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -9302,6 +9311,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/papaparse": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", diff --git a/app/package-lock.json b/app/package-lock.json index 17587a50..103ed851 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -35,6 +35,7 @@ "jwt-decode": "^3.1.2", "moment": "^2.29.4", "moment-duration-format": "^2.3.2", + "papaparse": "^5.4.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router": "^5.3.4", @@ -47,6 +48,7 @@ "@types/autosuggest-highlight": "^3.2.0", "@types/moment-duration-format": "^2.2.3", "@types/node": "^20.5.7", + "@types/papaparse": "^5.3.8", "@types/react": "^18.2.21", "@types/react-dom": "^18.0.10", "@vitejs/plugin-legacy": "^4.0.2", @@ -4091,6 +4093,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", "integrity": "sha512-dP7f3LdZIysZnmvP3ANJYTSwg+wLLl8p7RqniVlV7j+oXSXAbt9h0WIBFmJy5inWZoX9wZN6eXx+YXd9Rh3RBA==" }, + "node_modules/@types/papaparse": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.8.tgz", + "integrity": "sha512-ArKIEOOWULbhi53wkAiRy1ze4wvrTfhpAj7Yfzva+EkmX2sV8PpFB+xqzJfzXNzK4me95FJH9QZt5NXFVGzOoQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -9511,6 +9522,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/papaparse": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", diff --git a/app/package.json b/app/package.json index 7aa79f05..a20c7ca4 100644 --- a/app/package.json +++ b/app/package.json @@ -43,6 +43,7 @@ "jwt-decode": "^3.1.2", "moment": "^2.29.4", "moment-duration-format": "^2.3.2", + "papaparse": "^5.4.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router": "^5.3.4", @@ -55,6 +56,7 @@ "@types/autosuggest-highlight": "^3.2.0", "@types/moment-duration-format": "^2.2.3", "@types/node": "^20.5.7", + "@types/papaparse": "^5.3.8", "@types/react": "^18.2.21", "@types/react-dom": "^18.0.10", "@vitejs/plugin-legacy": "^4.0.2", diff --git a/app/public/csv.svg b/app/public/csv.svg new file mode 100644 index 00000000..d67cea7a --- /dev/null +++ b/app/public/csv.svg @@ -0,0 +1,49 @@ + + + + diff --git a/app/src/App.tsx b/app/src/App.tsx index c0bb9d90..25f12a18 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -194,16 +194,22 @@ const App: React.FC = () => { console.warn( "Access token expired, try to fetch from refresh token.." ); - await refreshToken(userAddress!, localStorage); - const userProfile = await getUserProfile( - userAddress!, - localStorage - ); - if (userProfile) setUserProfile(userProfile); - setUserProfiles( - await loadUserProfiles(Tezos, userAddress!, localStorage) - ); - break; + try { + await refreshToken(userAddress!, localStorage); + const userProfile = await getUserProfile( + userAddress!, + localStorage + ); + if (userProfile) setUserProfile(userProfile); + setUserProfiles( + await loadUserProfiles(Tezos, userAddress!, localStorage) + ); + break; + } catch (error) { + console.warn("Cannot refresh token, disconnect", error); + disconnectWallet(); + break; + } } } } else { diff --git a/app/src/components/CreatePermissionedSimplePoll.tsx b/app/src/components/CreatePermissionedSimplePoll.tsx index db3c6849..dad4ec0c 100644 --- a/app/src/components/CreatePermissionedSimplePoll.tsx +++ b/app/src/components/CreatePermissionedSimplePoll.tsx @@ -1,3 +1,5 @@ +import Papa from "papaparse"; + import { IonAvatar, IonButton, @@ -480,6 +482,59 @@ const CreatePermissionedSimplePoll: React.FC = () => { ) : ( "" )} + + + + + ) => { + const data = e.target.files + ? e.target.files[0] + : null; + + if (!data) { + presentAlert( + "Enter a valid CSV file, only first column with Tezos addresses, no header" + ); + } else { + let newBakerDelegators: string[] = []; + Papa.parse(data, { + header: false, + + step: (row) => { + const address = (row.data as Array)[0]; + if (!validateAddress(address)) { + presentAlert( + "Enter a valid Tezos address (" + + address + + ") on the first column of the CSV file, no header please" + ); + } + newBakerDelegators.push(address); + }, + complete: () => { + setContract({ + ...contract, + registeredVoters: [ + ...new Set([ + ...contract.registeredVoters, + ...newBakerDelegators, + ]), + ], + } as PermissionedSimplePollVotingContract); + }, + }); + } + + e.preventDefault(); + }} + /> + diff --git a/app/src/components/Settings.tsx b/app/src/components/Settings.tsx index 57698fd4..74ba2fb0 100644 --- a/app/src/components/Settings.tsx +++ b/app/src/components/Settings.tsx @@ -1,9 +1,4 @@ import { Share } from "@capacitor/share"; -import { - TezosTemplate3WalletType, - Storage as TezosTemplateVotingContract, -} from "../tezosTemplate3.types"; - import { IonAvatar, IonButton, @@ -40,6 +35,7 @@ import { shareSocialOutline, trashBinOutline, } from "ionicons/icons"; +import Papa from "papaparse"; import React, { useEffect, useRef, useState } from "react"; import { RouteComponentProps, useHistory } from "react-router-dom"; import { PAGES, UserContext, UserContextType } from "../App"; @@ -56,6 +52,10 @@ import { Storage as PermissionedSimplePollVotingContract, PermissionedSimplePollWalletType, } from "../permissionedSimplePoll.types"; +import { + TezosTemplate3WalletType, + Storage as TezosTemplateVotingContract, +} from "../tezosTemplate3.types"; import { Capacitor } from "@capacitor/core"; import { @@ -198,7 +198,7 @@ export const Settings: React.FC = ({ match }) => { } else { presentAlert({ header: "Warning", - message: "All delegators already added", + message: "All voters already added", }); setLoading(false); } @@ -731,6 +731,57 @@ export const Settings: React.FC = ({ match }) => { ) : ( "" )} + + + + + + ) => { + const data = e.target.files + ? e.target.files[0] + : null; + + if (!data) { + presentAlert( + "Enter a valid CSV file, only first column with Tezos addresses, no header" + ); + } else { + let newBakerDelegators: string[] = []; + Papa.parse(data, { + header: false, + + step: (row) => { + const address = ( + row.data as Array + )[0]; + if (!validateAddress(address)) { + presentAlert( + "Enter a valid Tezos address (" + + address + + ") on the first column of the CSV file, no header please" + ); + } + newBakerDelegators.push(address); + }, + complete: () => { + handleAddDelegatorVoters( + newBakerDelegators + ); + }, + }); + } + + e.preventDefault(); + }} + /> +