Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add support us modal #1620

Merged
merged 12 commits into from
Nov 2, 2024
6 changes: 5 additions & 1 deletion packages/extension-polkagate/src/components/TwoButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ interface Props {
onPrimaryClick: React.MouseEventHandler<HTMLButtonElement>;
secondaryBtnText?: string;
onSecondaryClick: React.MouseEventHandler<HTMLButtonElement>;
primaryBtnStartIcon?: React.JSX.Element;
secondaryBtnStartIcon?: React.JSX.Element;
mt?: string;
ml?: string;
disabled?: boolean;
Expand All @@ -25,7 +27,7 @@ interface Props {
}
// TODO: can replace ButtonWithCancel later

export default function TwoButtons ({ disabled = false, isBusy = false, ml = '6%', mt, onPrimaryClick, onSecondaryClick, primaryBtnText, secondaryBtnText, variant = 'outlined', width = '88%' }: Props): React.ReactElement<Props> {
export default function TwoButtons ({ disabled = false, isBusy = false, ml = '6%', mt, onPrimaryClick, onSecondaryClick, primaryBtnStartIcon, primaryBtnText, secondaryBtnStartIcon, secondaryBtnText, variant = 'outlined', width = '88%' }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const theme = useTheme();

Expand All @@ -35,6 +37,7 @@ export default function TwoButtons ({ disabled = false, isBusy = false, ml = '6%
<Button
disabled={isBusy}
onClick={onSecondaryClick}
startIcon={secondaryBtnStartIcon}
sx={{
borderColor: 'secondary.main',
color: variant === 'text' ? 'secondary.main' : 'text.primary',
Expand All @@ -59,6 +62,7 @@ export default function TwoButtons ({ disabled = false, isBusy = false, ml = '6%
: <Button
disabled={disabled}
onClick={onPrimaryClick}
startIcon={primaryBtnStartIcon}
sx={{
borderColor: 'secondary.main',
borderRadius: '5px',
Expand Down
227 changes: 227 additions & 0 deletions packages/extension-polkagate/src/fullscreen/governance/SupportUs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0

/* eslint-disable react/jsx-max-props-per-line */

import { AccessTime as AccessTimeIcon, ArrowForward as ArrowForwardIcon, Handshake as HandshakeIcon } from '@mui/icons-material';
import { alpha, Box, Button, Dialog, DialogContent, Paper, Slide, type Theme, Typography, useTheme } from '@mui/material';
import { styled } from '@mui/system';
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';

import { BN, BN_ZERO } from '@polkadot/util';

import { AccountsAssetsContext } from '../../components';
import { getStorage, setStorage } from '../../components/Loading';
import { useMyVote, useReferendum, useTranslation } from '../../hooks';
import { tieAccount } from '../../messaging';
import { POLKADOT_GENESIS_HASH } from '../../util/constants';
import { openOrFocusTab } from '../accountDetails/components/CommonTasks';
import { ENDED_STATUSES } from './utils/consts';

const PROPOSAL_NO = 1264;
const TRACK_ID = 33;
const SHOW_INTERVAL = 10 * 1000; // ms
const STORAGE_LABEL = `polkaGateVoteReminderLastShown_${PROPOSAL_NO}`;

const StyledPaper = styled(Paper)(({ theme }: { theme: Theme }) => ({
background: theme.palette.background.default,
borderRadius: 24,
boxShadow: '0 12px 24px rgba(0, 0, 0, 0.1)',
overflow: 'hidden',
padding: theme.spacing(4),
position: 'relative'
}));

const BackgroundDecoration = styled(Box)(({ theme }: { theme: Theme }) => ({
background: `linear-gradient(135deg, ${theme.palette.primary.main}, ${theme.palette.secondary.main})`,
clipPath: 'polygon(0 0, 100% 0, 100% 100%, 0 80%)',
height: '40%',
left: 0,
position: 'absolute',
right: 0,
top: 0,
zIndex: 0
}));

const ContentWrapper = styled(Box)(() => ({
position: 'relative',
zIndex: 1
}));

const StyledImage = styled('img')({
borderRadius: 16,
boxShadow: '0 8px 16px rgba(0, 0, 0, 0.1)',
height: 'auto',
width: '425px'
});

const VoteButton = styled(Button)(({ theme }: { theme: Theme }) => ({
'&:hover': {
backgroundColor: theme.palette.secondary.main,
boxShadow: '0 6px 16px rgba(0, 0, 0, 0.2)',
transform: 'translateY(-2px)'
},
backgroundColor: theme.palette.secondary.main,
borderRadius: 30,
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
color: '#fff',
fontSize: '16px',
fontWeight: 500,
padding: '10px 40px',
transition: 'all 0.3s ease-in-out'
}));

const MaybeLaterButton = styled(Button)(({ theme }: { theme: Theme }) => ({
'&:hover': {
backgroundColor: alpha(theme.palette.text.primary, 0.1)
},
color: theme.palette.text.primary,
padding: '10px',
textTransform: 'none'
}));

export default function SupportUs () {
const { t } = useTranslation();
const theme = useTheme();

const { accountsAssets } = useContext(AccountsAssetsContext);

const [open, setOpen] = useState<boolean>(true);
const [maxPowerAddress, setAddress] = useState<string>();
const [timeToNextShow, setTimeToNextShow] = useState<boolean>();

const referendum = useReferendum(maxPowerAddress, 'Referenda', PROPOSAL_NO);

const vote = useMyVote(maxPowerAddress, PROPOSAL_NO, TRACK_ID);
const notVoted = useMemo(() => vote === null || (vote && !('standard' in vote || 'splitAbstain' in vote || ('delegating' in vote && vote?.delegating?.voted))), [vote]);
Nick-1979 marked this conversation as resolved.
Show resolved Hide resolved
Nick-1979 marked this conversation as resolved.
Show resolved Hide resolved

const isReferendumOngoing = referendum?.status && !ENDED_STATUSES.includes(referendum.status);

const showModal = timeToNextShow && maxPowerAddress && notVoted && isReferendumOngoing;

useEffect(() => {
getStorage(STORAGE_LABEL).then((maybeDate) => {
const isWaitingExpired = Date.now() - (maybeDate as unknown as number) > SHOW_INTERVAL;

(!maybeDate || isWaitingExpired) && setTimeToNextShow(true);
}).catch(console.error);
}, []);

useEffect(() => {
if (!accountsAssets) {
return;
}

const balances = accountsAssets.balances;
let addressWithMaxVotingPower: string | undefined;
let max = BN_ZERO;

Object.keys(balances).forEach((address) => {
const maybeAsset = balances[address]?.[POLKADOT_GENESIS_HASH];

if (!maybeAsset) {
return;
}

const votingBalance = maybeAsset[0].votingBalance ? new BN(maybeAsset[0].votingBalance) : BN_ZERO;

if (votingBalance.gt(max)) {
max = votingBalance;
addressWithMaxVotingPower = address;
}
});

addressWithMaxVotingPower && tieAccount(addressWithMaxVotingPower, POLKADOT_GENESIS_HASH).finally(() => {
setAddress(addressWithMaxVotingPower);
}).catch(console.error);
Nick-1979 marked this conversation as resolved.
Show resolved Hide resolved
}, [accountsAssets]);

const handleOnVote = useCallback(() => {
maxPowerAddress && openOrFocusTab(`/governance/${maxPowerAddress}/referenda/${PROPOSAL_NO}`);
}, [maxPowerAddress]);
Nick-1979 marked this conversation as resolved.
Show resolved Hide resolved

const handleMaybeLater = useCallback(() => {
setStorage(STORAGE_LABEL, Date.now()).catch(console.error);
setOpen(false);
}, []);
Nick-1979 marked this conversation as resolved.
Show resolved Hide resolved

return (
<>
{showModal &&
<Dialog
PaperProps={{
style: {
background: 'transparent',
borderRadius: 24,
overflow: 'hidden'
}
}}
TransitionComponent={Slide}
TransitionProps={{
timeout: { enter: 500, exit: 300 }
}}
fullWidth
maxWidth='sm'
// onClose={handleMaybeLater}
open={open}
slotProps={{
backdrop: {
style: {
backdropFilter: 'blur(5px)',
backgroundColor: 'rgba(0, 0, 0, 0.4)'
}
}
}}
>
<DialogContent style={{ padding: 0 }}>
<StyledPaper theme={theme}>
<BackgroundDecoration theme={theme} />
<ContentWrapper>
<Box alignItems='center' display='flex' mb={3}>
<HandshakeIcon sx={{ color: theme.palette.approval.contrastText, fontSize: 40, mr: 2 }} />
<Typography fontSize='35px' fontWeight={500} sx={{ color: theme.palette.approval.contrastText }}>
{t('Support PolkaGate')}
</Typography>
</Box>
<Typography fontSize='16px' fontWeight={400} sx={{ color: '#fff', pb: '20px' }}>
{t("We're seeking retroactive funding to sustain and expand PolkaGate's impact!")}
</Typography>
<StyledImage
alt='Support PolkaGate'
src='/images/supportUs.jpeg'
/>
<Box mt={3}>
<Typography fontSize='30px' fontWeight={500} sx={{ color: theme.palette.text.primary, pb: '10px' }}>
{t('Your Vote Matters')}
</Typography>
<Typography fontSize='16px' fontWeight={400} sx={{ color: theme.palette.text.primary, pb: '10px' }}>
{t('Empower us to continue delivering valuable improvements and innovations.')}
</Typography>
<Typography fontSize='14px' fontWeight={400} sx={{ color: theme.palette.text.primary, fontStyle: 'italic' }}>
{t("Voting won't spend your tokens—they'll just be temporarily locked based on your chosen conviction level.")}
</Typography>
</Box>
<Box alignItems='center' display='flex' justifyContent='space-between' mt={4}>
<MaybeLaterButton
onClick={handleMaybeLater}
startIcon={<AccessTimeIcon />}
theme={theme}
>
{t("I'll think about it")}
</MaybeLaterButton>
<VoteButton
endIcon={<ArrowForwardIcon />}
onClick={handleOnVote}
theme={theme}
>
{t('Vote Now')}
</VoteButton>
</Box>
</ContentWrapper>
</StyledPaper>
</DialogContent>
</Dialog>
}
</>
);
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0
// @ts-nocheck

//@ts-nocheck

Nick-1979 marked this conversation as resolved.
Show resolved Hide resolved
import type { ApiPromise } from '@polkadot/api';
import type { AccountId32 } from '@polkadot/types/interfaces/runtime';
import type { PalletConvictionVotingVoteVoting } from '@polkadot/types/lookup';
import type { BN } from '@polkadot/util';
import type { Track } from '../../utils/types';

import { ApiPromise } from '@polkadot/api';
import { BN } from '@polkadot/util';

import { Track } from '../../utils/types';
import { toCamelCase } from '../../utils/util';

export const CONVICTION = {
Expand Down Expand Up @@ -71,8 +71,8 @@ interface Voting {
delegating: any; // needs to be fixed
}

export async function getAddressVote(address: string, api: ApiPromise, referendumIndex: number, trackId: number): Promise<Vote | null> {
const voting = await api.query.convictionVoting.votingFor(address, trackId) as unknown as PalletConvictionVotingVoteVoting;
export async function getAddressVote (address: string, api: ApiPromise, referendumIndex: number, trackId: number): Promise<Vote | null> {
const voting = await api.query['convictionVoting']['votingFor'](address, trackId) as unknown as PalletConvictionVotingVoteVoting;
Nick-1979 marked this conversation as resolved.
Show resolved Hide resolved

if (voting.isEmpty) {
return null;
Expand All @@ -96,7 +96,7 @@ export async function getAddressVote(address: string, api: ApiPromise, referendu
if (voting.isDelegating) {
// Then, look into the votes of the delegating target address.
const { conviction, target } = voting.asDelegating;
const proxyVoting = await api.query.convictionVoting.votingFor(target, trackId) as unknown as PalletConvictionVotingVoteVoting;
const proxyVoting = await api.query['convictionVoting']['votingFor'](target, trackId) as unknown as PalletConvictionVotingVoteVoting;
const targetVote = proxyVoting.isCasting ? proxyVoting.asCasting.votes.find(([index]) => index.toNumber() === referendumIndex)?.[1] : undefined;

if (!targetVote?.isStandard && !targetVote?.isSplitAbstain) {
Expand Down Expand Up @@ -139,8 +139,8 @@ export async function getAddressVote(address: string, api: ApiPromise, referendu
return null;
}

export async function getAllVotes(address: string, api: ApiPromise, tracks: Track[]): Promise<number[] | null> {
const queries = tracks.map((t) => api.query.convictionVoting.votingFor(address, t[0]));
export async function getAllVotes (address: string, api: ApiPromise, tracks: Track[]): Promise<number[] | null> {
const queries = tracks.map((t) => api.query['convictionVoting']['votingFor'](address, t[0]));
const voting = await Promise.all(queries);
const castedRefIndexes = voting?.map((v) => {
const jsonV = v.toJSON() as unknown as Voting;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { AccountContext, ActionContext } from '../../components';
import { useAccountsOrder, useAlerts, useFullscreen, useProfileAccounts, useTranslation } from '../../hooks';
import { AddNewAccountButton } from '../../partials';
import FullScreenHeader from '../governance/FullScreenHeader';
import SupportUs from '../governance/SupportUs';
import HeaderComponents from './components/HeaderComponents';
import DraggableAccountsList from './partials/DraggableAccountList';
import HomeMenu from './partials/HomeMenu';
Expand Down Expand Up @@ -89,6 +90,7 @@ function HomePageFullScreen (): React.ReactElement {
</Grid>
</Grid>
</Grid>
<SupportUs />
</Grid>
);
}
Expand Down
16 changes: 9 additions & 7 deletions packages/extension-polkagate/src/hooks/useMyVote.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0
// @ts-nocheck

import type React from 'react';
import type { Vote } from '../fullscreen/governance/post/myVote/util';

import { useCallback, useEffect, useState } from 'react';

import { getAddressVote, Vote } from '../fullscreen/governance/post/myVote/util';
import { useApi, useFormatted } from '.';
import { getAddressVote } from '../fullscreen/governance/post/myVote/util';
import { useInfo } from '.';

export default function useMyVote(
export default function useMyVote (
address: string | undefined,
refIndex: number | string | undefined,
trackId: number | string | undefined,
refresh?: boolean,
setRefresh?: React.Dispatch<React.SetStateAction<boolean>>
): Vote | null | undefined {
const api = useApi(address);
const formatted = useFormatted(address);
const { api, formatted } = useInfo(address);
Nick-1979 marked this conversation as resolved.
Show resolved Hide resolved

const [vote, setVote] = useState<Vote | null | undefined>();

const fetchVote = useCallback(async () => {
Expand All @@ -36,7 +38,7 @@ export default function useMyVote(
}, [fetchVote]);

useEffect(() => {
refresh && fetchVote();
refresh && fetchVote().catch(console.error);
}, [fetchVote, refresh]);

return vote;
Expand Down
Binary file added packages/extension/public/images/supportUs.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 11 additions & 1 deletion packages/extension/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -1411,5 +1411,15 @@
"Not recoverable": "",
"No proxy": "",
"Switch Theme": "",
"Account Icon": ""
"Account Icon": "",
"Support PolkaGate!": "",
"We’re seeking retroactive funding to sustain and expand PolkaGate’s impact! Your vote can empower us to continue delivering valuable improvements and innovations. Voting won’t spend your tokens—they’ll just be temporarily locked based on your chosen conviction level.": "",
"Vote to Support Us": "",
"Support PolkaGate": "",
"We're seeking retroactive funding to sustain and expand PolkaGate's impact!": "",
"Your Vote Matters": "",
"Empower us to continue delivering valuable improvements and innovations.": "",
"Voting won't spend your tokens—they'll just be temporarily locked based on your chosen conviction level.": "",
"I'll think about it": "",
"Vote Now": ""
}
Loading
Loading