{
return (
<>
-
+
@@ -29,8 +29,10 @@ export const SelectedContainer = ({
-
+
{error && {error.message}}
diff --git a/governance/ui/src/components/UserProfileCard/UserProfileDetails.tsx b/governance/ui/src/components/UserProfileCard/UserProfileDetails.tsx
index 84c5f8c7b..f85e31b59 100644
--- a/governance/ui/src/components/UserProfileCard/UserProfileDetails.tsx
+++ b/governance/ui/src/components/UserProfileCard/UserProfileDetails.tsx
@@ -1,7 +1,6 @@
import { CloseIcon, CopyIcon } from '@chakra-ui/icons';
import { Flex, IconButton, Button, Text, Tooltip } from '@chakra-ui/react';
import { prettyString } from '@snx-v3/format';
-import { shortAddress } from '../../utils/address';
import { Socials } from '../Socials';
import { GetUserDetails } from '../../queries/useGetUserDetailsQuery';
import { CouncilSlugs } from '../../utils/councils';
@@ -57,10 +56,10 @@ export const UserProfileDetails = ({
right="0px"
/>
-
+
- {shortAddress(userData?.address)}
+ {prettyString(userData!.address)}
@@ -111,7 +110,7 @@ export const UserProfileDetails = ({
colorScheme="gray"
mb="1"
w="100%"
- onClick={() => navigate(`/councils/${activeCouncil}?editProfile=true`)}
+ onClick={() => navigate(`/profile`)}
color="white"
>
Edit Profile
@@ -133,6 +132,7 @@ export const UserProfileDetails = ({
colorScheme="gray"
w="100%"
color="white"
+ data-cy="nominate-self-button-user-profile-details"
onClick={() =>
navigate(
`/councils/${activeCouncil}?${
@@ -149,6 +149,7 @@ export const UserProfileDetails = ({
{councilPeriod === '2' && (
)}
diff --git a/governance/ui/src/components/UserProfileForm/UserProfileEditPreview.tsx b/governance/ui/src/components/UserProfileForm/UserProfileEditPreview.tsx
new file mode 100644
index 000000000..332076602
--- /dev/null
+++ b/governance/ui/src/components/UserProfileForm/UserProfileEditPreview.tsx
@@ -0,0 +1,89 @@
+import { Button, Flex, Text } from '@chakra-ui/react';
+import { ProfilePicture } from '../UserProfileCard/ProfilePicture';
+import { prettyString } from '@snx-v3/format';
+import { GetUserDetails } from '../../queries';
+import { Socials } from '../Socials';
+import { CopyIcon } from '@chakra-ui/icons';
+
+export default function UserProfileEditPreview({
+ userData,
+ activeWallet,
+ isPending,
+ onSave,
+}: {
+ onSave: () => void;
+ isPending: boolean;
+ activeWallet?: string;
+ userData: GetUserDetails;
+}) {
+ return (
+
+
+
+
+
+
+ {userData.username ? userData.username : prettyString(activeWallet || '')}
+
+
+
+ {userData?.about}
+
+
+
+
+
+
+
+
+ Wallet Address
+
+
+
+
+ Governance Pitch
+
+
+ {userData?.delegationPitch}
+
+
+
+ );
+}
diff --git a/governance/ui/src/components/UserProfileForm/UserProfileForm.tsx b/governance/ui/src/components/UserProfileForm/UserProfileForm.tsx
index edd3c5abe..bf738e9d7 100644
--- a/governance/ui/src/components/UserProfileForm/UserProfileForm.tsx
+++ b/governance/ui/src/components/UserProfileForm/UserProfileForm.tsx
@@ -2,29 +2,34 @@ import { CloseIcon, CopyIcon } from '@chakra-ui/icons';
import {
Button,
Flex,
+ Heading,
IconButton,
Input,
+ Modal,
+ ModalContent,
+ ModalOverlay,
+ Show,
Spinner,
Text,
Textarea,
Tooltip,
+ useDisclosure,
} from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
-import { useNavigate } from 'react-router-dom';
import useUpdateUserDetailsMutation from '../../mutations/useUpdateUserDetailsMutation';
-import { useGetUserDetailsQuery } from '../../queries/';
+import { GetUserDetails, useGetUserDetailsQuery } from '../../queries/';
import { useEffect } from 'react';
import { useWallet } from '../../queries/useWallet';
import { ProfilePicture } from '../UserProfileCard/ProfilePicture';
+import UserProfileEditPreview from './UserProfileEditPreview';
-export function UserProfileForm({ activeCouncil }: { activeCouncil: string }) {
- const navigate = useNavigate();
+export function UserProfileForm() {
+ const { isOpen, onClose, onOpen } = useDisclosure();
const { activeWallet } = useWallet();
-
const { data: user, isLoading } = useGetUserDetailsQuery(activeWallet?.address);
const mutation = useUpdateUserDetailsMutation();
- const { register, getValues, setValue } = useForm({
+ const { register, getValues, setValue, watch } = useForm({
defaultValues: {
address: user?.about,
username: user?.username,
@@ -37,6 +42,8 @@ export function UserProfileForm({ activeCouncil }: { activeCouncil: string }) {
},
});
+ const userData = watch() as GetUserDetails;
+
useEffect(() => {
if (user) {
setValue('username', user.username);
@@ -49,6 +56,30 @@ export function UserProfileForm({ activeCouncil }: { activeCouncil: string }) {
}
}, [user, setValue]);
+ const handleOnFormSave = () => {
+ mutation.mutateAsync({
+ about: getValues('about')!,
+ address: user!.address,
+ email: '',
+ associatedAddresses: '',
+ bannerImageId: '',
+ bannerUrl: '',
+ delegationPitch: getValues('delegationPitch')!,
+ discord: getValues('discord')!,
+ ens: '',
+ github: getValues('github')!,
+ bannerThumbnailUrl: '',
+ pfpUrl: getValues('pfpUrl')!,
+ website: '',
+ pfpImageId: '',
+ twitter: getValues('twitter')!,
+ pfpThumbnailUrl: '',
+ notificationPreferences: '',
+ type: '',
+ username: getValues('username')!,
+ });
+ };
+
if (isLoading)
return (
@@ -57,147 +88,174 @@ export function UserProfileForm({ activeCouncil }: { activeCouncil: string }) {
);
return (
-
- navigate(`/councils/${activeCouncil}`)}
- size="xs"
- aria-label="close button"
- icon={}
- variant="ghost"
- colorScheme="whiteAlpha"
- color="white"
- position="absolute"
- top="5px"
- right="5px"
- />
-
-
-
-
- Avatar
+
+
+
+
+
+
+ Avatar
+
+
+
+
+
+
+ Username
+
+ About
+
+
-
-
-
- Username
-
-
-
- About
-
-
-
-
-
- Discord
-
-
-
-
-
- Twitter
-
-
-
-
-
- Github
-
-
-
-
-
- Wallet Address
+
+
+ Discord
+
+
+
+
+
+ Twitter
+
+
+
+
+
+ Github
+
+
+
+
+
+ Wallet Address
+
+
+
+
+
+
+ Governance Pitch
-
+
+
+
-
+
+
+
+
+ onClose()}
+ size="xs"
+ aria-label="close button"
+ icon={}
+ variant="ghost"
+ colorScheme="whiteAlpha"
+ color="white"
+ position="absolute"
+ top="10px"
+ right="10px"
+ />
+ {
+ handleOnFormSave();
+ }}
+ />
+
+
+
+
-
-
- Governance Pitch
-
-
-
- {mutation.isPending ? (
-
- loading
-
+
+
+ Preview
+ {
+ handleOnFormSave();
+ }}
+ />
- ) : (
-
- )}
+
);
}
diff --git a/governance/ui/src/components/UserProfileForm/index.ts b/governance/ui/src/components/UserProfileForm/index.ts
index 9c47c71f3..7607761ed 100644
--- a/governance/ui/src/components/UserProfileForm/index.ts
+++ b/governance/ui/src/components/UserProfileForm/index.ts
@@ -1 +1,2 @@
export * from './UserProfileForm';
+export * from './UserProfileEditPreview';
diff --git a/governance/ui/src/components/UserTableView/UserTableView.tsx b/governance/ui/src/components/UserTableView/UserTableView.tsx
index 2a546b886..475ccd5c9 100644
--- a/governance/ui/src/components/UserTableView/UserTableView.tsx
+++ b/governance/ui/src/components/UserTableView/UserTableView.tsx
@@ -1,12 +1,12 @@
import { Button, Flex, Spinner, Th, Tr } from '@chakra-ui/react';
import { GetUserDetails } from '../../queries/useGetUserDetailsQuery';
-import { shortAddress } from '../../utils/address';
import { Badge } from '../Badge';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { useGetCurrentPeriod } from '../../queries/useGetCurrentPeriod';
import { CouncilSlugs } from '../../utils/councils';
import { useGetElectionSettings } from '../../queries/useGetElectionSettings';
import { ProfilePicture } from '../UserProfileCard/ProfilePicture';
+import { prettyString } from '@snx-v3/format';
export default function UserTableView({
user,
@@ -27,6 +27,7 @@ export default function UserTableView({
if (!user) return ;
return (
navigate(`/councils/${activeCouncil}?view=${user.address}`)}
_hover={{ background: 'rgba(255,255,255,0.12)' }}
@@ -35,10 +36,8 @@ export default function UserTableView({
borderColor={searchParams.get('view') === user.address ? 'cyan.500' : 'gray.900'}
rounded="base"
>
- {councilPeriod === '2' && (
-
- #{councilSettings && councilSettings?.epochSeatCount > place ? place + 1 : '-'}
- |
+ {(councilPeriod === '2' || councilPeriod === '0') && (
+ {place < 10 ? `#${place + 1}` : '-'} |
)}
{' '}
- {user.username ? user.username : shortAddress(user.address)}
- |
-
-
- Nominee
- {councilSettings && councilSettings?.epochSeatCount > place && (
- Member
- )}
-
+ {user.username ? user.username : prettyString(user.address, 8, 8)}
|
- {councilPeriod === '2' && TODO | }
- {councilPeriod !== '2' && (
+ {councilPeriod !== '0' && (
+
+
+ Nominee
+ {councilSettings && councilSettings?.epochSeatCount > place && (
+ Member
+ )}
+
+ |
+ )}
+ {(councilPeriod === '2' || councilPeriod === '0') && TODO | }
+ {(councilPeriod === '2' || councilPeriod === '0') && TODO | }
+ {councilPeriod !== '2' && councilPeriod !== '0' && (
|
)}
+ {councilPeriod === '0' && (
+
+ Your Vote TODO
+ |
+ )}
);
}
diff --git a/governance/ui/src/context/VoteContext.tsx b/governance/ui/src/context/VoteContext.tsx
index 86019c218..a4e16118c 100644
--- a/governance/ui/src/context/VoteContext.tsx
+++ b/governance/ui/src/context/VoteContext.tsx
@@ -31,11 +31,6 @@ const voteReducer = (state: VoteState, action: Action): VoteState => {
...state,
spartan: action.payload,
};
- case 'GRANTS':
- return {
- ...state,
- grants: action.payload,
- };
case 'AMBASSADOR':
return {
...state,
diff --git a/governance/ui/src/hooks/useCountdown.tsx b/governance/ui/src/hooks/useCountdown.tsx
new file mode 100644
index 000000000..62ccfa209
--- /dev/null
+++ b/governance/ui/src/hooks/useCountdown.tsx
@@ -0,0 +1,37 @@
+import { useState, useEffect } from 'react';
+
+const calculateTimeLeft = (timestamp: number) => {
+ const now = new Date();
+ const targetDate = new Date(timestamp * 1000);
+ const difference = targetDate.getTime() - now.getTime();
+
+ let timeLeft = {} as { days: number; minutes: number; hours: number };
+
+ if (difference > 0) {
+ timeLeft = {
+ days: Math.floor(difference / (1000 * 60 * 60 * 24)),
+ hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
+ minutes: Math.floor((difference / 1000 / 60) % 60),
+ };
+ } else {
+ timeLeft = { days: 0, hours: 0, minutes: 0 };
+ }
+
+ return timeLeft;
+};
+
+const useCountdown = (timestamp: number) => {
+ const [timeLeft, setTimeLeft] = useState(calculateTimeLeft(timestamp));
+
+ useEffect(() => {
+ const timer = setInterval(() => {
+ setTimeLeft(calculateTimeLeft(timestamp));
+ }, 60000);
+
+ return () => clearInterval(timer);
+ }, [timestamp]);
+
+ return timeLeft;
+};
+
+export default useCountdown;
diff --git a/governance/ui/src/hooks/useGetUserSelectedVotes.ts b/governance/ui/src/hooks/useGetUserSelectedVotes.ts
index 1f4e891d6..7bf0dcb4a 100644
--- a/governance/ui/src/hooks/useGetUserSelectedVotes.ts
+++ b/governance/ui/src/hooks/useGetUserSelectedVotes.ts
@@ -1,16 +1,35 @@
import { useMemo } from 'react';
import { useVoteContext } from '../context/VoteContext';
+import { useGetCouncilNominees } from '../queries';
+import { CouncilSlugs } from '../utils/councils';
export const useGetUserSelectedVotes = () => {
const { state } = useVoteContext();
+ const { data: spartanNominees } = useGetCouncilNominees('spartan');
+ const { data: ambassadorNominees } = useGetCouncilNominees('ambassador');
+ const { data: treasuryNominees } = useGetCouncilNominees('treasury');
- return useMemo(
- () => ({
- spartan: state.spartan,
- grants: state.grants,
- ambassador: state.ambassador,
- treasury: state.treasury,
- }),
- [state.ambassador, state.grants, state.spartan, state.treasury]
- );
+ const localstorageState = localStorage.getItem('voteSelection');
+
+ return useMemo(() => {
+ const parsedLocalstorageState = localstorageState ? JSON.parse(localstorageState) : {};
+ const allNominees = spartanNominees
+ ?.concat(ambassadorNominees || [])
+ .concat(treasuryNominees || []);
+
+ const whichState = (council: CouncilSlugs) => {
+ if (
+ parsedLocalstorageState[council] &&
+ allNominees?.includes(parsedLocalstorageState[council])
+ )
+ return parsedLocalstorageState[council];
+ return state[council];
+ };
+
+ return {
+ spartan: whichState('spartan'),
+ ambassador: whichState('ambassador'),
+ treasury: whichState('treasury'),
+ };
+ }, [localstorageState, spartanNominees, ambassadorNominees, treasuryNominees, state]);
};
diff --git a/governance/ui/src/mutations/useEditNomination.ts b/governance/ui/src/mutations/useEditNomination.ts
index 5beb940bd..63164c98d 100644
--- a/governance/ui/src/mutations/useEditNomination.ts
+++ b/governance/ui/src/mutations/useEditNomination.ts
@@ -4,6 +4,7 @@ import { getCouncilContract } from '../utils/contracts';
import { CouncilSlugs } from '../utils/councils';
import { CustomToast } from '../components/CustomToast';
import { useToast } from '@chakra-ui/react';
+import { devSigner } from '../utils/providers';
export default function useEditNomination({
currentNomination,
@@ -12,7 +13,7 @@ export default function useEditNomination({
currentNomination?: CouncilSlugs;
nextNomination?: CouncilSlugs;
}) {
- const queryClient = useQueryClient();
+ const query = useQueryClient();
const signer = useSigner();
const toast = useToast();
@@ -21,19 +22,36 @@ export default function useEditNomination({
if (signer) {
if ((nextNomination && currentNomination) || (!nextNomination && currentNomination)) {
const tx1 = await getCouncilContract(currentNomination)
- .connect(signer)
+ .connect(process.env.DEV === 'true' ? devSigner : signer)
.withdrawNomination();
await tx1.wait();
}
if (nextNomination) {
- const tx2 = await getCouncilContract(nextNomination).connect(signer).nominate();
+ const tx2 = await getCouncilContract(nextNomination)
+ .connect(process.env.DEV === 'true' ? devSigner : signer)
+ .nominate();
await tx2.wait();
}
}
},
mutationKey: ['editNomination', currentNomination, nextNomination],
onSuccess: async () => {
- await queryClient.refetchQueries({ queryKey: ['isNominated'] });
+ const address = await signer?.getAddress();
+ await query.invalidateQueries({
+ queryKey: ['isNominated', address?.toLowerCase()],
+ exact: false,
+ });
+ await query.invalidateQueries({ queryKey: ['nominees', currentNomination] });
+ await query.invalidateQueries({ queryKey: ['nominees', nextNomination] });
+ await query.invalidateQueries({ queryKey: ['nomineesDetails', currentNomination] });
+ await query.invalidateQueries({ queryKey: ['nomineesDetails', nextNomination] });
+ await query.refetchQueries({ queryKey: ['nominees', currentNomination], exact: false });
+ await query.refetchQueries({ queryKey: ['nominees', nextNomination], exact: false });
+ await query.refetchQueries({
+ queryKey: ['nomineesDetails', currentNomination],
+ exact: false,
+ });
+ await query.refetchQueries({ queryKey: ['nomineesDetails', nextNomination], exact: false });
toast({
description: 'Nomination successfully edited.',
status: 'success',
diff --git a/governance/ui/src/mutations/useNominateSelf.ts b/governance/ui/src/mutations/useNominateSelf.ts
index fd8e61f2c..faa243c8b 100644
--- a/governance/ui/src/mutations/useNominateSelf.ts
+++ b/governance/ui/src/mutations/useNominateSelf.ts
@@ -4,6 +4,7 @@ import { getCouncilContract } from '../utils/contracts';
import { useSigner } from '../queries/useWallet';
import { useToast } from '@chakra-ui/react';
import { CustomToast } from '../components/CustomToast';
+import { devSigner } from '../utils/providers';
export default function useNominateSelf(council: CouncilSlugs, address?: string) {
const query = useQueryClient();
@@ -13,13 +14,21 @@ export default function useNominateSelf(council: CouncilSlugs, address?: string)
return useMutation({
mutationFn: async () => {
if (signer) {
- const tx = await getCouncilContract(council).connect(signer).nominate();
+ const tx = await getCouncilContract(council)
+ .connect(process.env.DEV === 'true' ? devSigner : signer)
+ .nominate();
await tx.wait();
}
},
mutationKey: ['nomination', council, address],
onSuccess: async () => {
- await query.refetchQueries({ queryKey: ['isNominated', address], exact: false });
+ await query.invalidateQueries({
+ queryKey: ['isNominated', address],
+ });
+ await query.invalidateQueries({ queryKey: ['nominees', council], exact: false });
+ await query.invalidateQueries({ queryKey: ['nomineesDetails', council], exact: false });
+ await query.refetchQueries({ queryKey: ['nominees', council], exact: false });
+ await query.refetchQueries({ queryKey: ['nomineesDetails', council], exact: false });
toast({
description: 'Successfully nominated yourself.',
status: 'success',
diff --git a/governance/ui/src/pages/Admin.tsx b/governance/ui/src/pages/Admin.tsx
index f5ae92077..a3c9d6030 100644
--- a/governance/ui/src/pages/Admin.tsx
+++ b/governance/ui/src/pages/Admin.tsx
@@ -1,17 +1,21 @@
import { Button, Flex, Heading, Text } from '@chakra-ui/react';
-import { Contract, utils } from 'ethers';
+import { Wallet } from 'ethers';
import { useSigner } from '../queries/useWallet';
import { SnapshotRecordContractAddress, getCouncilContract } from '../utils/contracts';
+import { motherShipProvider } from '../utils/providers';
export default function Admin() {
const signer = useSigner();
+ const wallet = new Wallet(
+ '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',
+ motherShipProvider
+ );
return (
{[
getCouncilContract('spartan'),
getCouncilContract('ambassador'),
- getCouncilContract('grants'),
getCouncilContract('treasury'),
].map((proxy, index) => {
return (
@@ -63,6 +67,22 @@ export default function Admin() {
block.timestamp + 20000
);
});
+ } else {
+ try {
+ wallet.provider.getBlock('latest').then((block) => {
+ proxy
+ .connect(wallet)
+ .Epoch_setEpochDates(
+ 0,
+ block.timestamp - 10,
+ block.timestamp,
+ block.timestamp + 10000,
+ block.timestamp + 20000
+ );
+ });
+ } catch (error) {
+ console.error(error);
+ }
}
}}
>
@@ -85,6 +105,22 @@ export default function Admin() {
block.timestamp + 10000
);
});
+ } else {
+ try {
+ wallet.provider.getBlock('latest').then((block) => {
+ proxy
+ .connect(wallet)
+ .Epoch_setEpochDates(
+ 0,
+ block.timestamp - 200,
+ block.timestamp - 100,
+ block.timestamp,
+ block.timestamp + 10000
+ );
+ });
+ } catch (error) {
+ console.error(error);
+ }
}
}}
>
@@ -127,28 +163,6 @@ export default function Admin() {