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();
+ }}
+ />
+