diff --git a/__mocks__/getRepresentatives/handlers.ts b/__mocks__/getRepresentatives/handlers.ts
index f5e5c59..7bbf3fb 100644
--- a/__mocks__/getRepresentatives/handlers.ts
+++ b/__mocks__/getRepresentatives/handlers.ts
@@ -10,7 +10,6 @@ export const getRepresentativesHandlers = [
is_alternate: false,
workshop_id: BigInt(1).toString(),
email: 'john@johnson.com',
- color: '#000000',
wallet_address: 'addr1isdufnpoasidjfopmaimdfmopisadj',
name: 'John Johnson',
},
@@ -21,7 +20,6 @@ export const getRepresentativesHandlers = [
is_alternate: true,
workshop_id: BigInt(1).toString(),
email: 'mike@mickelson.com',
- color: '#000000',
wallet_address: 'addr1oqwieuroijfvaondsfipoaapoidjf',
name: 'Mike Mickelson',
},
@@ -32,7 +30,6 @@ export const getRepresentativesHandlers = [
is_alternate: false,
workshop_id: BigInt(2).toString(),
email: 'jack@jackson.com',
- color: '#000000',
wallet_address: 'addr1kidjfsadjfoadspjfoaidsfadsop',
name: 'Jack Jackson',
},
@@ -43,7 +40,6 @@ export const getRepresentativesHandlers = [
is_alternate: true,
workshop_id: BigInt(2).toString(),
email: 'rob@robertson.com',
- color: '#000000',
wallet_address: 'addr1iwoiuwqxpoieujsoidpjfkfjhpqowmfoa',
name: 'Robert Robertson',
},
@@ -54,7 +50,6 @@ export const getRepresentativesHandlers = [
is_alternate: false,
workshop_id: BigInt(3).toString(),
email: 'connor@connorson.com',
- color: '#000000',
wallet_address: 'addr1opijpoiuoimnlkjipoujpoimlkkm',
name: 'Connor Connorson',
},
@@ -65,7 +60,6 @@ export const getRepresentativesHandlers = [
is_alternate: true,
workshop_id: BigInt(3).toString(),
email: 'kyle@kyleson.com',
- color: '#000000',
wallet_address: 'addr1adfasdfwerwqersdvfzxfvsadf',
name: 'Kyle Kyleson',
},
diff --git a/__mocks__/updateUser/errorHandlers.ts b/__mocks__/updateUser/errorHandlers.ts
index 1b1ebce..10c3d96 100644
--- a/__mocks__/updateUser/errorHandlers.ts
+++ b/__mocks__/updateUser/errorHandlers.ts
@@ -4,7 +4,7 @@ export const updateUserActiveVoteHandler = [
http.post('/api/updateUser', async () => {
return HttpResponse.json(
{
- pollId: BigInt(-1).toString(),
+ userId: BigInt(-1).toString(),
message:
'You cannot update user information while a Poll is actively voting.',
},
@@ -17,7 +17,7 @@ export const updateUserNoNameHandler = [
http.post('/api/updateUser', async () => {
return HttpResponse.json(
{
- pollId: BigInt(-1).toString(),
+ userId: BigInt(-1).toString(),
message: 'Name must be provided.',
},
{ status: 400 },
@@ -29,7 +29,7 @@ export const updateUserNoEmailHandler = [
http.post('/api/updateUser', async () => {
return HttpResponse.json(
{
- pollId: BigInt(-1).toString(),
+ userId: BigInt(-1).toString(),
message: 'Email must be provided.',
},
{ status: 400 },
@@ -41,7 +41,7 @@ export const updateUserNoWalletAddressHandler = [
http.post('/api/updateUser', async () => {
return HttpResponse.json(
{
- pollId: BigInt(-1).toString(),
+ userId: BigInt(-1).toString(),
message: 'Wallet address must be provided.',
},
{ status: 400 },
@@ -53,7 +53,7 @@ export const updateUserTooLongNameHandler = [
http.post('/api/updateUser', async () => {
return HttpResponse.json(
{
- pollId: BigInt(-1).toString(),
+ userId: BigInt(-1).toString(),
message: 'Name must be less than 100 characters.',
},
{ status: 400 },
@@ -65,7 +65,7 @@ export const updateUserTooLongEmailHandler = [
http.post('/api/updateUser', async () => {
return HttpResponse.json(
{
- pollId: BigInt(-1).toString(),
+ userId: BigInt(-1).toString(),
message: 'Email must be less than 100 characters.',
},
{ status: 400 },
@@ -77,7 +77,7 @@ export const updateUserTooLongWalletAddressHandler = [
http.post('/api/updateUser', async () => {
return HttpResponse.json(
{
- pollId: BigInt(-1).toString(),
+ userId: BigInt(-1).toString(),
message: 'Wallet Address must be less than 100 characters.',
},
{ status: 400 },
@@ -89,7 +89,7 @@ export const updateUserInternalErrorHandler = [
http.post('/api/updateUser', async () => {
return HttpResponse.json(
{
- pollId: BigInt(-1).toString(),
+ userId: BigInt(-1).toString(),
message: 'Error updating User Information.',
},
{ status: 500 },
@@ -101,7 +101,7 @@ export const updateUserInvalidSessionHandler = [
http.post('/api/updateUser', async () => {
return HttpResponse.json(
{
- success: false,
+ userId: BigInt(-1).toString(),
message:
'You must be signed in as an Organizer update user information.',
},
@@ -114,7 +114,7 @@ export const updateUserNotOrganizerHandler = [
http.post('/api/updateUser', async () => {
return HttpResponse.json(
{
- success: false,
+ userId: BigInt(-1).toString(),
message: 'You must be an Organizer to update user information.',
},
{ status: 401 },
diff --git a/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollHasFinishedVoting.test.tsx b/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollHasFinishedVoting.test.tsx
index a8175c7..f2d44d5 100644
--- a/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollHasFinishedVoting.test.tsx
+++ b/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollHasFinishedVoting.test.tsx
@@ -19,6 +19,9 @@ test.skip('alerts user when poll has finished voting', async () => {
id: '1',
stakeAddress: 'stakeAddress',
walletName: 'walletName',
+ isCoordinator: false,
+ isDelegate: true,
+ isAlternate: false,
},
}}
>
@@ -26,7 +29,6 @@ test.skip('alerts user when poll has finished voting', async () => {
diff --git a/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollIdIsNotProvided.test.tsx b/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollIdIsNotProvided.test.tsx
index cded9a4..f13d46d 100644
--- a/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollIdIsNotProvided.test.tsx
+++ b/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollIdIsNotProvided.test.tsx
@@ -19,6 +19,9 @@ test.skip('alerts user when poll ID is not provided', async () => {
id: '1',
stakeAddress: 'stakeAddress',
walletName: 'walletName',
+ isCoordinator: false,
+ isDelegate: true,
+ isAlternate: false,
},
}}
>
@@ -26,7 +29,6 @@ test.skip('alerts user when poll ID is not provided', async () => {
diff --git a/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollIsArchived.test.tsx b/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollIsArchived.test.tsx
index 55b96b5..beb7b23 100644
--- a/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollIsArchived.test.tsx
+++ b/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollIsArchived.test.tsx
@@ -19,6 +19,9 @@ test.skip('alerts user when poll is archived', async () => {
id: '1',
stakeAddress: 'stakeAddress',
walletName: 'walletName',
+ isCoordinator: false,
+ isDelegate: true,
+ isAlternate: false,
},
}}
>
@@ -26,7 +29,6 @@ test.skip('alerts user when poll is archived', async () => {
diff --git a/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollIsNotFound.test.tsx b/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollIsNotFound.test.tsx
index 5230585..187930c 100644
--- a/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollIsNotFound.test.tsx
+++ b/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollIsNotFound.test.tsx
@@ -19,6 +19,9 @@ test.skip('alerts user when poll is not found', async () => {
id: '1',
stakeAddress: 'stakeAddress',
walletName: 'walletName',
+ isCoordinator: false,
+ isDelegate: true,
+ isAlternate: false,
},
}}
>
@@ -26,7 +29,6 @@ test.skip('alerts user when poll is not found', async () => {
diff --git a/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollIsNotOpenForVotingYet.test.tsx b/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollIsNotOpenForVotingYet.test.tsx
index 439d675..aefde1c 100644
--- a/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollIsNotOpenForVotingYet.test.tsx
+++ b/__tests__/components/buttons/voteOnPollButton/alertsUserWhenPollIsNotOpenForVotingYet.test.tsx
@@ -19,6 +19,9 @@ test.skip('alerts user when poll is not open for voting yet', async () => {
id: '1',
stakeAddress: 'stakeAddress',
walletName: 'walletName',
+ isCoordinator: false,
+ isDelegate: true,
+ isAlternate: false,
},
}}
>
@@ -26,7 +29,6 @@ test.skip('alerts user when poll is not open for voting yet', async () => {
diff --git a/__tests__/components/buttons/voteOnPollButton/alertsUserWhenVoteFailsToCast.test.tsx b/__tests__/components/buttons/voteOnPollButton/alertsUserWhenVoteFailsToCast.test.tsx
index 8f6d9b2..24051da 100644
--- a/__tests__/components/buttons/voteOnPollButton/alertsUserWhenVoteFailsToCast.test.tsx
+++ b/__tests__/components/buttons/voteOnPollButton/alertsUserWhenVoteFailsToCast.test.tsx
@@ -19,6 +19,9 @@ test.skip('alerts user when poll has finished voting', async () => {
id: '1',
stakeAddress: 'stakeAddress',
walletName: 'walletName',
+ isCoordinator: false,
+ isDelegate: true,
+ isAlternate: false,
},
}}
>
@@ -26,7 +29,6 @@ test.skip('alerts user when poll has finished voting', async () => {
diff --git a/__tests__/components/buttons/voteOnPollButton/alertsUserWhenVoteIsInvalid.test.tsx b/__tests__/components/buttons/voteOnPollButton/alertsUserWhenVoteIsInvalid.test.tsx
index 088d3df..ccefb64 100644
--- a/__tests__/components/buttons/voteOnPollButton/alertsUserWhenVoteIsInvalid.test.tsx
+++ b/__tests__/components/buttons/voteOnPollButton/alertsUserWhenVoteIsInvalid.test.tsx
@@ -19,6 +19,9 @@ test.skip('alerts user when vote is invalid', async () => {
id: '1',
stakeAddress: 'stakeAddress',
walletName: 'walletName',
+ isCoordinator: false,
+ isDelegate: true,
+ isAlternate: false,
},
}}
>
@@ -26,7 +29,6 @@ test.skip('alerts user when vote is invalid', async () => {
diff --git a/__tests__/components/buttons/voteOnPollButton/alertsUserWhenVoteIsNotProvided.test.tsx b/__tests__/components/buttons/voteOnPollButton/alertsUserWhenVoteIsNotProvided.test.tsx
index b0ace13..3ce9c46 100644
--- a/__tests__/components/buttons/voteOnPollButton/alertsUserWhenVoteIsNotProvided.test.tsx
+++ b/__tests__/components/buttons/voteOnPollButton/alertsUserWhenVoteIsNotProvided.test.tsx
@@ -19,6 +19,9 @@ test.skip('alerts user when vote is not provided', async () => {
id: '1',
stakeAddress: 'stakeAddress',
walletName: 'walletName',
+ isCoordinator: false,
+ isDelegate: true,
+ isAlternate: false,
},
}}
>
@@ -26,7 +29,6 @@ test.skip('alerts user when vote is not provided', async () => {
diff --git a/__tests__/components/buttons/voteOnPollButton/successfullyVotesAbstainOnAPoll.test.tsx b/__tests__/components/buttons/voteOnPollButton/successfullyVotesAbstainOnAPoll.test.tsx
index 191531a..914e858 100644
--- a/__tests__/components/buttons/voteOnPollButton/successfullyVotesAbstainOnAPoll.test.tsx
+++ b/__tests__/components/buttons/voteOnPollButton/successfullyVotesAbstainOnAPoll.test.tsx
@@ -16,6 +16,9 @@ test.skip('successfully votes abstain on a poll', async () => {
id: '1',
stakeAddress: 'stakeAddress',
walletName: 'walletName',
+ isCoordinator: false,
+ isDelegate: true,
+ isAlternate: false,
},
}}
>
@@ -23,7 +26,6 @@ test.skip('successfully votes abstain on a poll', async () => {
diff --git a/__tests__/components/buttons/voteOnPollButton/successfullyVotesNoAPoll.test.tsx b/__tests__/components/buttons/voteOnPollButton/successfullyVotesNoAPoll.test.tsx
index 0049ae4..94ca4c0 100644
--- a/__tests__/components/buttons/voteOnPollButton/successfullyVotesNoAPoll.test.tsx
+++ b/__tests__/components/buttons/voteOnPollButton/successfullyVotesNoAPoll.test.tsx
@@ -16,6 +16,9 @@ test.skip('successfully votes no on a poll', async () => {
id: '1',
stakeAddress: 'stakeAddress',
walletName: 'walletName',
+ isCoordinator: false,
+ isDelegate: true,
+ isAlternate: false,
},
}}
>
@@ -23,7 +26,6 @@ test.skip('successfully votes no on a poll', async () => {
diff --git a/__tests__/components/buttons/voteOnPollButton/successfullyVotesYesOnAPoll.test.tsx b/__tests__/components/buttons/voteOnPollButton/successfullyVotesYesOnAPoll.test.tsx
index 571bd5a..c83f329 100644
--- a/__tests__/components/buttons/voteOnPollButton/successfullyVotesYesOnAPoll.test.tsx
+++ b/__tests__/components/buttons/voteOnPollButton/successfullyVotesYesOnAPoll.test.tsx
@@ -16,6 +16,9 @@ test.skip('successfully votes yes on a poll', async () => {
id: '1',
stakeAddress: 'stakeAddress',
walletName: 'walletName',
+ isCoordinator: false,
+ isDelegate: true,
+ isAlternate: false,
},
}}
>
@@ -23,7 +26,6 @@ test.skip('successfully votes yes on a poll', async () => {
diff --git a/__tests__/components/coordinator/manageRepresentativesTable/stripsWhitespaceFromWalletAddress.test.tsx b/__tests__/components/coordinator/manageRepresentativesTable/stripsWhitespaceFromWalletAddress.test.tsx
new file mode 100644
index 0000000..a0bbb19
--- /dev/null
+++ b/__tests__/components/coordinator/manageRepresentativesTable/stripsWhitespaceFromWalletAddress.test.tsx
@@ -0,0 +1,33 @@
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { Toaster } from 'react-hot-toast';
+import { expect, test } from 'vitest';
+
+import { ManageRepresentativesTable } from '@/components/coordinator/manageRepresentativesTable';
+
+test('strips whitespace from wallet address', async () => {
+ const user = userEvent.setup();
+ render(
+ <>
+
+ {}} />
+ >,
+ );
+
+ const editButton = await screen.findByTestId('edit-representative-info-1');
+ await user.click(editButton);
+
+ const input = screen.getByDisplayValue('addr1isdufnpoasidjfopmaimdfmopisadj');
+ await user.clear(input);
+ await user.type(input, ' coStakeAddress ');
+
+ const saveButton = await screen.findByTestId('save-representative-info-1');
+ await user.click(saveButton);
+
+ // Wait for the success toast to appear
+ const successToast = await screen.findByText('User info updated!');
+ expect(successToast).toBeDefined();
+
+ const newWalletAddress = await screen.findByText('coStakeAddress');
+ expect(newWalletAddress).toBeDefined();
+});
diff --git a/package-lock.json b/package-lock.json
index 03f548b..d691661 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"name": "cardano-constitution-voting-app",
"version": "0.1.0",
"hasInstallScript": true,
+ "license": "AGPL-3.0-or-later",
"dependencies": {
"@claritydao/clarity-backend": "^0.4.0-dev-512cc333547522c7d53ffe0ecddacd3de3958dd1",
"@emotion/react": "^11.13.3",
diff --git a/public/cardano.png b/public/cardano.png
new file mode 100644
index 0000000..dbdcf49
Binary files /dev/null and b/public/cardano.png differ
diff --git a/src/components/buttons/connectWalletButton.tsx b/src/components/buttons/connectWalletButton.tsx
index b7fd30f..36f3051 100644
--- a/src/components/buttons/connectWalletButton.tsx
+++ b/src/components/buttons/connectWalletButton.tsx
@@ -17,11 +17,16 @@ import { paths } from '@/paths';
import { connectWallet } from '@/lib/connectWallet';
import { getUser } from '@/lib/helpers/getUser';
+interface Props {
+ isHomepage?: boolean;
+}
+
/**
* A button to connect a wallet to a variety of cip-30 compatible wallets
* @returns Connect Wallet Button
*/
-export function ConnectWalletButton(): JSX.Element {
+export function ConnectWalletButton(props: Props): JSX.Element {
+ const { isHomepage } = props;
const [open, setOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState(null);
const [connecting, setConnecting] = useState(false);
@@ -189,8 +194,24 @@ export function ConnectWalletButton(): JSX.Element {
)}
- {session.status === 'authenticated' && (
+ {isHomepage ? (
+ <>>
+ ) : session.status === 'authenticated' ? (
Wallet connected
+ ) : (
+
+ Wallet not connected
+
)}
+
{wallets}
);
diff --git a/src/components/buttons/downloadPollVotesButton.tsx b/src/components/buttons/downloadPollVotesButton.tsx
index 3acf3bc..60acb66 100644
--- a/src/components/buttons/downloadPollVotesButton.tsx
+++ b/src/components/buttons/downloadPollVotesButton.tsx
@@ -33,11 +33,12 @@ export function DownloadPollVotesButton(props: Props): JSX.Element {
return (
);
}
diff --git a/src/components/buttons/downloadUserVotesButton.tsx b/src/components/buttons/downloadUserVotesButton.tsx
index 7f74d5d..5b43f1b 100644
--- a/src/components/buttons/downloadUserVotesButton.tsx
+++ b/src/components/buttons/downloadUserVotesButton.tsx
@@ -33,11 +33,12 @@ export function DownloadUserVotesButton(props: Props): JSX.Element {
return (
);
}
diff --git a/src/components/buttons/voteOnPollButtons.tsx b/src/components/buttons/voteOnPollButtons.tsx
index 2520f34..c955c35 100644
--- a/src/components/buttons/voteOnPollButtons.tsx
+++ b/src/components/buttons/voteOnPollButtons.tsx
@@ -9,12 +9,12 @@ import { useSession } from 'next-auth/react';
import toast from 'react-hot-toast';
import { castVote } from '@/lib/helpers/castVote';
+import { getActiveVoterFromUserId } from '@/lib/helpers/getActiveVoterFromUserId';
import { getPollVote } from '@/lib/helpers/getPollVote';
interface Props {
pollName: string;
pollId: string;
- isActiveVoter: boolean;
hashedText: string;
link: string;
}
@@ -23,17 +23,33 @@ interface Props {
* Yes, No, Abstain buttons to vote on a poll
* @param pollName - The name of the poll
* @param pollId - The ID of the poll
- * @param isActiveVoter - Whether the user is the active voter
+ * @param hashedText - The hashed text of the poll
+ * @param link - The link to the poll
* @returns Vote on Poll Buttons
*/
export function VoteOnPollButtons(props: Props): JSX.Element {
- const { pollName, pollId, isActiveVoter, hashedText, link } = props;
- const [vote, setVote] = useState('');
- const [disabled, setDisabled] = useState(false);
+ const { pollName, pollId, hashedText, link } = props;
const session = useSession();
const theme = useTheme();
+ const [vote, setVote] = useState('');
+ const [disabled, setDisabled] = useState(false);
+ const [isActiveVoter, setIsActiveVoter] = useState(false);
+ const [fetching, setIsFetching] = useState(
+ session.status === 'authenticated' ? false : true,
+ );
+
+ useEffect(() => {
+ if (session.data?.user.id) {
+ setIsFetching(true);
+ getActiveVoterFromUserId(session.data.user.id).then((result) => {
+ setIsActiveVoter(result.activeVoterId === session.data.user.id);
+ setIsFetching(false);
+ });
+ }
+ }, [session.data?.user.id]);
+
async function handleVote(vote: string): Promise {
setDisabled(true);
const result = await castVote(
@@ -91,7 +107,7 @@ export function VoteOnPollButtons(props: Props): JSX.Element {
)}
- {!isActiveVoter && !session.data?.user.isCoordinator && (
+ {!isActiveVoter && !session.data?.user.isCoordinator && !fetching && (
You are not the active voter for your workshop. Only the active
voter can vote.
@@ -116,7 +132,9 @@ export function VoteOnPollButtons(props: Props): JSX.Element {
variant="outlined"
color="success"
sx={{
- width: '150px',
+ width: '170px',
+ py: 2,
+ borderRadius: '60px !important',
}}
endIcon={}
size="large"
@@ -124,13 +142,16 @@ export function VoteOnPollButtons(props: Props): JSX.Element {
disabled={disabled || !isActiveVoter}
data-testid="vote-yes-button"
>
- Yes
+ Vote Yes
}
size="large"
@@ -138,12 +159,14 @@ export function VoteOnPollButtons(props: Props): JSX.Element {
disabled={disabled || !isActiveVoter}
data-testid="vote-no-button"
>
- No
+ Vote No
}
size="large"
diff --git a/src/components/coordinator/manageActivePowerTable.tsx b/src/components/coordinator/manageActivePowerTable.tsx
index f49d6b6..a40fa77 100644
--- a/src/components/coordinator/manageActivePowerTable.tsx
+++ b/src/components/coordinator/manageActivePowerTable.tsx
@@ -27,11 +27,19 @@ import { getRepresentatives } from '@/lib/helpers/getRepresentatives';
import { getWorkshops } from '@/lib/helpers/getWorkshops';
import { updateActiveVoter } from '@/lib/helpers/updateActiveVoter';
+interface Props {
+ refresh: boolean;
+ toggleRefresh: () => void;
+}
+
/**
* Allows a workshop coordinator to manage if delegates or alternates have active power from each workhsop
+ * @param refresh - boolean to refresh the table
+ * @param toggleRefresh - function to toggle the refresh boolean
* @returns Admin Manage Active Power Table
*/
-export function ManageActivePowerTable(): JSX.Element {
+export function ManageActivePowerTable(props: Props): JSX.Element {
+ const { refresh, toggleRefresh } = props;
const [representatives, setRepresentatives] = useState([]);
const [workshops, setWorkshops] = useState([]);
const [rowModesModel, setRowModesModel] = useState({});
@@ -47,7 +55,7 @@ export function ManageActivePowerTable(): JSX.Element {
setRepresentatives(reps);
}
fetchData();
- }, [reload]);
+ }, [reload, refresh]);
function handleRowEditStop(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -69,6 +77,7 @@ export function ManageActivePowerTable(): JSX.Element {
toast.error(data.message);
}
setReload(!reload);
+ toggleRefresh();
return newRow;
}
@@ -271,7 +280,14 @@ export function ManageActivePowerTable(): JSX.Element {
},
},
];
- }, [representatives, rowModesModel, theme.palette.text.primary, reload]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [
+ representatives,
+ rowModesModel,
+ theme.palette.text.primary,
+ reload,
+ refresh, // TS is not recognizing refresh as a dependency but it is required to properly refresh the table when info in representatives table changes
+ ]);
return (
@@ -280,7 +296,7 @@ export function ManageActivePowerTable(): JSX.Element {
void;
+}
+
/**
* Table for admin to manage representative information
+ * @param toggleRefresh - function to toggle the refresh boolean
* @returns Admin Manage Representatives Table
*/
-export function ManageRepresentativesTable(): JSX.Element {
+export function ManageRepresentativesTable(props: Props): JSX.Element {
+ const { toggleRefresh } = props;
const [representatives, setRepresentatives] = useState([]);
const [rowModesModel, setRowModesModel] = useState({});
const [reload, setReload] = useState(false);
@@ -64,6 +70,7 @@ export function ManageRepresentativesTable(): JSX.Element {
toast.error(data.message);
}
setReload(!reload);
+ toggleRefresh();
return newRow;
}
@@ -186,13 +193,14 @@ export function ManageRepresentativesTable(): JSX.Element {
-
-
- Voting Tool
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ Voting Tool
+
+
+
+
+
);
diff --git a/src/components/polls/coordinatorViewVotes.tsx b/src/components/polls/coordinatorViewVotes.tsx
index 7c3429a..192037e 100644
--- a/src/components/polls/coordinatorViewVotes.tsx
+++ b/src/components/polls/coordinatorViewVotes.tsx
@@ -123,6 +123,7 @@ export function CoordinatorViewVotes(props: Props): JSX.Element {
row.id}
sx={{
@@ -131,7 +132,7 @@ export function CoordinatorViewVotes(props: Props): JSX.Element {
},
'.MuiDataGrid-columnHeader': {
backgroundColor: 'rgba(0, 0, 0, 0.1)',
- fontFamily: 'Montserrat',
+ fontFamily: 'Chivo',
fontSize: '1.2rem',
},
}}
diff --git a/src/components/polls/pollCard.tsx b/src/components/polls/pollCard.tsx
index 11a4b11..5b2f9ad 100644
--- a/src/components/polls/pollCard.tsx
+++ b/src/components/polls/pollCard.tsx
@@ -1,6 +1,6 @@
import Link from 'next/link';
import { pollPhases } from '@/constants/pollPhases';
-import LaunchRounded from '@mui/icons-material/LaunchRounded';
+import { ArrowForwardRounded } from '@mui/icons-material';
import { useTheme } from '@mui/material';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
@@ -54,7 +54,7 @@ export function PollCard(props: Props): JSX.Element {
mt={2}
>
View
-
+
diff --git a/src/components/polls/pollCarrousel.tsx b/src/components/polls/pollCarrousel.tsx
index 51665ba..4bef07a 100644
--- a/src/components/polls/pollCarrousel.tsx
+++ b/src/components/polls/pollCarrousel.tsx
@@ -10,6 +10,7 @@ import { PollCard } from '@/components/polls/pollCard';
interface Props {
currentPollId: string | string[] | undefined;
polls: Poll[];
+ isPollPage?: boolean;
}
/**
@@ -19,7 +20,7 @@ interface Props {
* @returns Poll List
*/
export function PollCarrousel(props: Props): JSX.Element {
- const { currentPollId, polls } = props;
+ const { currentPollId, polls, isPollPage } = props;
let pollsToDisplay = useMemo(() => [...polls], [polls]);
@@ -63,8 +64,15 @@ export function PollCarrousel(props: Props): JSX.Element {
}}
flexDirection="column"
width="100%"
+ justifyContent="center"
>
-
+
{pollsToDisplay.map((poll) => {
return (
-
- Browse other polls
+
+ Browse {isPollPage ? 'other ' : ''}polls
{pollCards}
diff --git a/src/components/polls/pollList.tsx b/src/components/polls/pollList.tsx
index 11a4df2..da4bdd0 100644
--- a/src/components/polls/pollList.tsx
+++ b/src/components/polls/pollList.tsx
@@ -65,7 +65,13 @@ export function PollList(props: Props): JSX.Element {
wallet:
)}
-
+
{pollCards}
diff --git a/src/components/polls/pollResults.tsx b/src/components/polls/pollResults.tsx
index 5f56875..f527c9d 100644
--- a/src/components/polls/pollResults.tsx
+++ b/src/components/polls/pollResults.tsx
@@ -9,7 +9,6 @@ import {
LinearProgress,
linearProgressClasses,
styled,
- Tooltip,
useTheme,
} from '@mui/material';
import Box from '@mui/material/Box';
@@ -157,7 +156,12 @@ export function PollResults(props: Props): JSX.Element {
return (
-
+
Results
diff --git a/src/components/representatives/representativesTable.tsx b/src/components/representatives/representativesTable.tsx
index 6d836cd..10b8da5 100644
--- a/src/components/representatives/representativesTable.tsx
+++ b/src/components/representatives/representativesTable.tsx
@@ -1,11 +1,11 @@
import Link from 'next/link';
-import LaunchRounded from '@mui/icons-material/LaunchRounded';
import { Box, useTheme } from '@mui/material';
import Typography from '@mui/material/Typography';
import { DataGrid, GridColDef } from '@mui/x-data-grid';
import type { User, Workshop } from '@/types';
import { paths } from '@/paths';
+import { abbreviateName } from '@/lib/helpers/abbreviateName';
interface Props {
representatives: User[];
@@ -61,6 +61,7 @@ export function RepresentativesTable(props: Props): JSX.Element {
const delegateId = params.row.delegate_id;
const delegate = representatives.find((rep) => rep.id === delegateId);
const activeVoterId = params.row.active_voter_id;
+ const name = abbreviateName(delegate?.name || '');
return (
- {delegate?.name}
+ {name}
-
);
@@ -118,6 +118,7 @@ export function RepresentativesTable(props: Props): JSX.Element {
const alternateId = params.row.alternate_id;
const alternate = representatives.find((rep) => rep.id === alternateId);
const activeVoterId = params.row.active_voter_id;
+ const name = abbreviateName(alternate?.name || '');
return (
- {alternate?.name}
+ {name}
-
);
@@ -178,6 +178,7 @@ export function RepresentativesTable(props: Props): JSX.Element {
const activeVoter = representatives.find(
(rep) => rep.id === activeVoterId,
);
+ const name = abbreviateName(activeVoter?.name || '');
return (
- {activeVoter?.name}
-
+ {name}
);
@@ -224,7 +224,7 @@ export function RepresentativesTable(props: Props): JSX.Element {
workshop.name !== 'Convention Organizer',
)}
columns={columns}
- initialState={{
- pagination: {
- paginationModel: {
- pageSize: 100,
- },
- },
- }}
- pageSizeOptions={[25, 50, 100]}
+ hideFooter
columnVisibilityModel={{
id: false,
}}
@@ -254,7 +247,7 @@ export function RepresentativesTable(props: Props): JSX.Element {
display: 'none',
},
'.MuiDataGrid-columnHeader': {
- fontFamily: 'Montserrat',
+ fontFamily: 'Chivo',
fontSize: '1.2rem',
},
'.MuiDataGrid-cell': {
diff --git a/src/components/representatives/votingHistoryTable.tsx b/src/components/representatives/votingHistoryTable.tsx
index 9cd64be..ed478d4 100644
--- a/src/components/representatives/votingHistoryTable.tsx
+++ b/src/components/representatives/votingHistoryTable.tsx
@@ -1,3 +1,4 @@
+import { pollPhases } from '@/constants/pollPhases';
import DoDisturbRounded from '@mui/icons-material/DoDisturbRounded';
import ThumbDownRounded from '@mui/icons-material/ThumbDownRounded';
import ThumbUpRounded from '@mui/icons-material/ThumbUpRounded';
@@ -78,6 +79,7 @@ export function VotingHistoryTable(props: Props): JSX.Element {
const userVoteData = votes.find(
(vote) => vote.poll_id === params.row.id,
);
+ const poll = polls.find((poll) => poll.id === params.row.id);
const userVote = userVoteData?.vote;
return (
)}
- {!userVote && (
+ {poll?.status === pollPhases.pending ||
+ (poll?.status === pollPhases.voting && (
+
+ In Progress
+
+ ))}
+ {poll?.status === pollPhases.concluded && !userVote && (
None
@@ -117,36 +125,33 @@ export function VotingHistoryTable(props: Props): JSX.Element {
return (
-
- Voting History
+
+ History
diff --git a/src/img/cc-logo.png b/src/img/cc-logo.png
new file mode 100644
index 0000000..5ce4afc
Binary files /dev/null and b/src/img/cc-logo.png differ
diff --git a/src/lib/getStringBytes.ts b/src/lib/getStringBytes.ts
new file mode 100644
index 0000000..5446347
--- /dev/null
+++ b/src/lib/getStringBytes.ts
@@ -0,0 +1,9 @@
+/**
+ * Gets the byte size of a string
+ * @param str - The string to get the byte size of
+ * @returns Number of bytes
+ */
+export function getStringBytes(str: string): number {
+ const byteSize = new Blob([str]).size;
+ return byteSize;
+}
diff --git a/src/lib/helpers/abbreviateName.ts b/src/lib/helpers/abbreviateName.ts
new file mode 100644
index 0000000..1a08712
--- /dev/null
+++ b/src/lib/helpers/abbreviateName.ts
@@ -0,0 +1,14 @@
+export function abbreviateName(fullName: string) {
+ const nameParts = fullName.split(' ');
+ if (nameParts.length < 2) {
+ return fullName; // If there's no last name, return the original name
+ }
+
+ const lastName = nameParts[nameParts.length - 1];
+ const initials = nameParts
+ .slice(0, -1)
+ .map((name) => name[0].toUpperCase() + '.')
+ .join('');
+
+ return `${initials} ${lastName}`;
+}
diff --git a/src/pages/404.tsx b/src/pages/404.tsx
new file mode 100644
index 0000000..a0b861f
--- /dev/null
+++ b/src/pages/404.tsx
@@ -0,0 +1,37 @@
+import Head from 'next/head';
+import Link from 'next/link';
+import HomeRounded from '@mui/icons-material/HomeRounded';
+import { Box, Button, Typography } from '@mui/material';
+
+export default function Custom404(): JSX.Element {
+ return (
+ <>
+
+ Constitutional Convention Voting App
+
+
+
+
+
+
+ 404 - Page Not Found
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx
index c916115..10d5646 100644
--- a/src/pages/_document.tsx
+++ b/src/pages/_document.tsx
@@ -9,11 +9,7 @@ export default function Document(): JSX.Element {
-
diff --git a/src/pages/api/auth/[...nextauth].ts b/src/pages/api/auth/[...nextauth].ts
index 8620890..caffa13 100644
--- a/src/pages/api/auth/[...nextauth].ts
+++ b/src/pages/api/auth/[...nextauth].ts
@@ -51,6 +51,7 @@ export const authOptions: NextAuthOptions = {
isCoordinator: user.is_convention_organizer,
isDelegate: user.is_delegate,
isAlternate: user.is_alternate,
+ name: user.name,
};
} else {
return null;
@@ -65,6 +66,7 @@ export const authOptions: NextAuthOptions = {
async jwt({ token, user }) {
if (user) {
token.stakeAddress = user.stakeAddress;
+ token.name = user.name;
token.walletName = user.walletName;
token.isCoordinator = user.isCoordinator;
token.isDelegate = user.isDelegate;
@@ -75,6 +77,9 @@ export const authOptions: NextAuthOptions = {
async session({ session, token }) {
if (typeof token.walletName === 'string')
session.user.walletName = token.walletName;
+ if (typeof token.name === 'string') {
+ session.user.name = token.name;
+ }
if (typeof token.isCoordinator === 'boolean')
session.user.isCoordinator = token.isCoordinator;
if (typeof token.isDelegate === 'boolean')
diff --git a/src/pages/api/updateUser.ts b/src/pages/api/updateUser.ts
index b1b9fec..e38456e 100644
--- a/src/pages/api/updateUser.ts
+++ b/src/pages/api/updateUser.ts
@@ -7,6 +7,7 @@ import { getServerSession } from 'next-auth';
import { checkIfCO } from '@/lib/checkIfCO';
import { checkIfVoting } from '@/lib/checkIfVoting';
+import { getStringBytes } from '@/lib/getStringBytes';
type Data = {
userId: string;
@@ -66,10 +67,10 @@ export default async function updateUser(
message: 'Name must be provided.',
});
}
- if (name.length > 100) {
+ if (getStringBytes(name) > 64) {
return res.status(400).json({
userId: BigInt(-1).toString(),
- message: 'Name must be less than 100 characters.',
+ message: 'Name must be less than 64 characters.',
});
}
// validate email
@@ -106,7 +107,7 @@ export default async function updateUser(
data: {
name: name,
email: email,
- wallet_address: wallet_address,
+ wallet_address: wallet_address.trim(),
},
});
return res.status(200).json({
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index 698a535..af881f7 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -34,13 +34,10 @@ export default function Home(props: Props): JSX.Element {
content="Voting app to be used by delegates at the Cardano Constitutional Convention in Buenos Aires to ratify the initial constitution. This voting app was commissioned by Intersect."
/>
-
+
-
+
)}
-
+
Welcome to the Constitutional Convention Voting Tool
Are you a delegate?
-
+
Connect a wallet to cast your vote:
-
+ {session.status === 'unauthenticated' && (
+
+ )}
-
+
@@ -185,15 +172,33 @@ export default function ViewPoll(props: Props): JSX.Element {
{poll ? (
-
- }
+
+
+ }
+ >
+ View Constitution Text
+
+
+
+
- View Constitution Text
-
+ The linked text document has the Blake2b-256 hash of:{' '}
+ {poll.hashedText}
+
{poll.summary_tx_id && !isTxUploading && (
@@ -289,9 +294,6 @@ export default function ViewPoll(props: Props): JSX.Element {
@@ -308,7 +310,11 @@ export default function ViewPoll(props: Props): JSX.Element {
{/* Browse Other Polls Carrousel */}
-
+
=> {
if (!context.params) {
@@ -395,23 +400,12 @@ export const getServerSideProps = async (
workshops: [],
pollResultsSSR: {},
polls: [],
- workshopActiveVoterId: '',
},
};
}
const { pollId } = context.params;
- const session = await getServerSession(context.req, context.res, authOptions);
-
- let workshopActiveVoterId = '';
- if (session) {
- const activeVoterId = await activeVoterDto(session.user.id);
- if (activeVoterId) {
- workshopActiveVoterId = activeVoterId;
- }
- }
-
const poll = await pollDto(pollId);
const representatives = await representativesDto();
const workshops = await workshopsDto();
@@ -425,7 +419,6 @@ export const getServerSideProps = async (
workshops: workshops,
pollResultsSSR: pollResultsSSR,
polls: polls,
- workshopActiveVoterId: workshopActiveVoterId,
},
};
};
diff --git a/src/pages/polls/new.tsx b/src/pages/polls/new.tsx
index 28d32bd..48d9591 100644
--- a/src/pages/polls/new.tsx
+++ b/src/pages/polls/new.tsx
@@ -38,10 +38,7 @@ export default function NewPoll(props: Props): JSX.Element {
content="Voting app to be used by delegates at the Cardano Constitutional Convention in Buenos Aires to ratify the initial constitution. This voting app was commissioned by Intersect."
/>
-
+
diff --git a/src/pages/representatives/[userId]/index.tsx b/src/pages/representatives/[userId]/index.tsx
index 61434fa..813269d 100644
--- a/src/pages/representatives/[userId]/index.tsx
+++ b/src/pages/representatives/[userId]/index.tsx
@@ -55,10 +55,7 @@ export default function Representative(props: Props): JSX.Element {
content="Voting app to be used by delegates at the Cardano Constitutional Convention in Buenos Aires to ratify the initial constitution. This voting app was commissioned by Intersect."
/>
-
+
@@ -128,20 +125,12 @@ export default function Representative(props: Props): JSX.Element {
color={theme.palette.text.primary}
>
-
+
{userVotes.length} vote
- {userVotes.length === 1 ? '' : 's'}
+ {userVotes.length === 1 ? '' : 's'} cast
-
+
{workshopName || 'Failed to retrieve workshop'}
@@ -163,6 +152,12 @@ export default function Representative(props: Props): JSX.Element {
xs: 12,
md: 6,
}}
+ sx={{
+ mt: {
+ xs: 6,
+ md: 0,
+ },
+ }}
>
{
+ setRefresh((prev) => !prev);
+ }, []);
return (
<>
@@ -22,17 +28,18 @@ export default function ManageRepresentatives(): JSX.Element {
content="Voting app to be used by delegates at the Cardano Constitutional Convention in Buenos Aires to ratify the initial constitution. This voting app was commissioned by Intersect."
/>
-
+
Coordinator Dashboard
-
-
-
-
+
+
>
diff --git a/src/providers/themeProvider.tsx b/src/providers/themeProvider.tsx
index 2e05fa1..c3f0226 100644
--- a/src/providers/themeProvider.tsx
+++ b/src/providers/themeProvider.tsx
@@ -75,7 +75,7 @@ export function ColorModeProvider({
styleOverrides: {
root: {
textTransform: 'unset',
- fontFamily: 'Inter',
+ fontFamily: 'Chivo',
borderRadius: '24px',
padding: '10px 30px',
fontWeight: 600,
@@ -89,7 +89,7 @@ export function ColorModeProvider({
backgroundColor: 'rgba(77,166,77, 0.1)',
},
outlinedWarning: {
- border: '1px solid rgb(245, 148, 77)',
+ border: '1px solid rgba(255,255,255, .10)',
backgroundColor: 'rgba(245, 148, 77, 0.1)',
},
outlinedPrimary: {
@@ -197,9 +197,8 @@ export function ColorModeProvider({
},
},
typography: {
- fontFamily: 'Montserrat',
+ fontFamily: 'Chivo',
button: {
- fontFamily: 'Inter',
fontWeight: 400,
lineHeight: 1.5,
},
@@ -228,11 +227,11 @@ export function ColorModeProvider({
color: '#FFFFFF',
},
body1: {
- fontFamily: 'Inter',
+ fontFamily: 'Chivo',
color: '#FFFFFF',
},
body2: {
- fontFamily: 'Inter',
+ fontFamily: 'Chivo',
color: '#FFFFFF',
},
},
@@ -244,6 +243,7 @@ export function ColorModeProvider({
<>