Skip to content

Commit

Permalink
fix: allow user to choose which wallet to use
Browse files Browse the repository at this point in the history
  • Loading branch information
veeso committed Mar 4, 2024
1 parent 407153f commit 7a67656
Show file tree
Hide file tree
Showing 14 changed files with 299 additions and 42 deletions.
8 changes: 4 additions & 4 deletions src/ekoke_erc20_swap_frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@
"prettier:check": "prettier --config .prettierrc --check src/"
},
"dependencies": {
"@dfinity/agent": "^1.0.1",
"@dfinity/candid": "^1.0.1",
"@dfinity/principal": "^1.0.1",
"@dfinity/agent": "^1.0",
"@dfinity/candid": "^1.0",
"@dfinity/principal": "^1.0",
"metamask-react": "^2.7.0",
"react": "^18.2",
"react-dom": "^18.2",
"react-feather": "^2.0",
"react-helmet": "^6.1",
"react-ic-wallet": "^0.2.1",
"react-ic-wallet": "^0.3.1",
"web3": "^4.5.0"
},
"devDependencies": {
Expand Down
23 changes: 17 additions & 6 deletions src/ekoke_erc20_swap_frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,25 @@ import { MetaMaskProvider } from 'metamask-react';
import { IcWalletProvider } from 'react-ic-wallet';

import AppLayout from './js/components/AppLayout';
import AppContextProvider, {
useAppContext,
} from './js/components/App/AppContext';

const App = () => (
<MetaMaskProvider>
<AppContextProvider>
<AppLayoutWrapper />
</AppContextProvider>
</MetaMaskProvider>
);

const AppLayoutWrapper = () => {
const { icWallet } = useAppContext();

const App = () => {
return (
<MetaMaskProvider>
<IcWalletProvider>
<AppLayout />
</IcWalletProvider>
</MetaMaskProvider>
<IcWalletProvider provider={icWallet}>
<AppLayout />
</IcWalletProvider>
);
};

Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
31 changes: 31 additions & 0 deletions src/ekoke_erc20_swap_frontend/src/js/components/App/AppContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as React from 'react';
import { WalletProvider } from 'react-ic-wallet';
import { getUserIcWallet } from '../../storage';

interface Context {
icWallet?: WalletProvider;
setIcWallet?: (icWallet: WalletProvider | undefined) => void;
}

const AppContext = React.createContext<Context>({});

const AppContextProvider = ({ children }: { children?: React.ReactNode }) => {
const [icWallet, setIcWallet] = React.useState<WalletProvider | undefined>();

React.useEffect(() => {
const userWallet = getUserIcWallet();
if (userWallet) {
setIcWallet(userWallet);
}
}, []);

return (
<AppContext.Provider value={{ icWallet, setIcWallet }}>
{children}
</AppContext.Provider>
);
};

export default AppContextProvider;

export const useAppContext = () => React.useContext(AppContext);
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { useIcWallet } from 'react-ic-wallet';

import ConnectMessage from './ConnectMessage';
import ConnectedPage from './ConnectedPage';
import { useAppContext } from './AppContext';

const LayoutSwitch = () => {
const { status: metamaskStatus } = useMetaMask();
const { status: icStatus } = useIcWallet();
const { icWallet } = useAppContext();

if (metamaskStatus === 'connected' && icStatus === 'connected') {
if (metamaskStatus === 'connected' && icStatus === 'connected' && icWallet) {
return <ConnectedPage />;
}

Expand Down
78 changes: 60 additions & 18 deletions src/ekoke_erc20_swap_frontend/src/js/components/IcConnect.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,92 @@
import * as React from 'react';
import { useIcWallet } from 'react-ic-wallet';
import { WalletProvider, useIcWallet } from 'react-ic-wallet';

import Button from './reusable/Button';
import Container from './reusable/Container';
import InternetComputer from './svg/InternetComputer';
import { useAppContext } from './App/AppContext';
import WalletSelector from './IcConnect/WalletSelector';
import { setUserIcWallet } from '../storage';
import DisconnectPopup from './IcConnect/DisconnectPopup';

const IcConnect = () => {
const { status, connect, disconnect, principal } = useIcWallet();
const { icWallet, setIcWallet } = useAppContext();
const [showWalletSelector, setShowWalletSelector] = React.useState(false);
const [showDisconnectPopup, setShowDisconnectPopup] = React.useState(false);

const disabled = ['initializing', 'unavailable', 'connecting'].includes(
status,
);

React.useEffect(() => {
console.log('status from ctx', status);
}, [status]);
const onDisconnect = () => {
if (setIcWallet) {
setIcWallet(undefined);
}
setUserIcWallet(undefined);

return disconnect();
};

const onClick = () => {
if (status === 'notConnected') {
return connect();
setShowWalletSelector(true);
} else if (status === 'connected') {
return disconnect();
setShowDisconnectPopup(true);
}
return undefined;
};

const onWalletSelect = (wallet: WalletProvider) => {
if (setIcWallet) {
setIcWallet(wallet);
}
setShowWalletSelector(false);
};

const text = () => {
if (status === 'initializing') return 'Initializing...';
if (status === 'unavailable') return 'IC Wallet not available';
if (status === 'notConnected') return 'Connect to IC';
if (status === 'notConnected') return 'Connect IC Wallet';
if (status === 'connecting') return 'Connecting...';
if (status === 'connected') return `${principal.substring(0, 18)}...`;
if (status === 'connected')
return `${principal.toString().substring(0, 18)}...`;
return undefined;
};

React.useEffect(() => {
console.log('ic status', status, 'ic wallet', icWallet);
if (icWallet !== undefined && status === 'notConnected') {
connect()
.then(() => {
setUserIcWallet(icWallet);
})
.catch((e) => {
console.error('Failed to connect to wallet', e);
});
}
}, [icWallet, status, connect]);

return (
<Container.FlexRow className="items-center gap-8">
<Button.Alternative
className="my-0 !mb-0"
onClick={onClick}
disabled={disabled}
>
<InternetComputer className="inline w-[32px] mr-2" />
{text()}
</Button.Alternative>
</Container.FlexRow>
<>
<Container.FlexRow className="items-center gap-8">
<Button.Alternative
className="my-0 !mb-0"
onClick={onClick}
disabled={disabled}
>
<InternetComputer className="inline w-[32px] mr-2" />
{text()}
</Button.Alternative>
</Container.FlexRow>
{showWalletSelector ? <WalletSelector onSelect={onWalletSelect} /> : null}
{showDisconnectPopup ? (
<DisconnectPopup
onDisconnect={onDisconnect}
onDismiss={() => setShowDisconnectPopup(false)}
/>
) : null}
</>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import * as React from 'react';

import Container from '../reusable/Container';
import Button from '../reusable/Button';

const DisconnectPopup = ({
onDisconnect,
onDismiss,
}: {
onDisconnect: () => void;
onDismiss: () => void;
}) => {
return (
<Container.Container className="h-screen left-0 overflow-hidden fixed right-0 top-0 w-screen z-50">
<Container.Container className="bg-gray-800/60 h-screen w-screen" />
<Container.Container className="bg-white bottom-0 h-fit sm:h-[70vh] sm:rounded-t-xl left-0 m-auto p-8 fixed right-0 top-0 sm:top-auto sm:bottom-0 w-fit min-w-[25%] sm:w-full">
<Container.FlexCols className="gap-4 text-lg p-4">
<span className="text-lg text-center block text-text">
Do you want to disconnect your wallet?
</span>
<Container.FlexResponsiveRow className="items-center justify-between gap-8">
<Button.Danger onClick={onDisconnect}>Disconnect</Button.Danger>
<Button.Alternative onClick={onDismiss}>Cancel</Button.Alternative>
</Container.FlexResponsiveRow>
</Container.FlexCols>
</Container.Container>
</Container.Container>
);
};

export default DisconnectPopup;
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import * as React from 'react';
import { WalletProvider } from 'react-ic-wallet';

import Container from '../reusable/Container';
import Button from '../reusable/Button';

import IcpWalletLogo from '../../../assets/images/icp-wallet.webp';
import BitfinityWalletLogo from '../../../assets/images/bitfinity-wallet.webp';
import PlugWalletLogo from '../../../assets/images/plug-wallet.webp';

const WalletSelector = ({
onSelect,
}: {
onSelect: (wallet: WalletProvider) => void;
}) => {
return (
<Container.Container className="h-screen left-0 overflow-hidden fixed right-0 top-0 w-screen z-50">
<Container.Container className="bg-gray-800/60 h-screen w-screen" />
<Container.Container className="bg-white bottom-0 h-fit sm:h-[70vh] sm:rounded-t-xl left-0 m-auto p-8 fixed right-0 top-0 sm:top-auto sm:bottom-0 w-fit min-w-[25%] sm:w-full">
<Container.FlexCols className="gap-4 text-lg p-4">
<span className="text-lg text-center block text-text">
Connect a wallet
</span>
<Container.Container className="grid grid-cols-2 sm:grid-cols-1 gap-4 px-4">
<Wallet
logo={IcpWalletLogo}
name="Internet Identity"
onSelect={onSelect}
wallet={WalletProvider.Dfinity}
/>
<Wallet
logo={BitfinityWalletLogo}
name="Bitfinity Wallet"
onSelect={onSelect}
wallet={WalletProvider.Bitfinity}
/>
<Wallet
logo={PlugWalletLogo}
name="Plug"
onSelect={onSelect}
wallet={WalletProvider.Plug}
/>
</Container.Container>
</Container.FlexCols>
</Container.Container>
</Container.Container>
);
};

const Wallet = ({
logo,
name,
onSelect,
wallet,
}: {
logo: string;
name: string;
onSelect: (wallet: WalletProvider) => void;
wallet: WalletProvider;
}) => (
<Container.Container>
<Button.Alternative className="w-full" onClick={() => onSelect(wallet)}>
<Container.FlexRow className="items-center justify-between w-full">
<span className="text-lg text-text">{name}</span>
<img
className="rounded-full bg-white border border-gray-300 p-1 w-[48px] h-[48px]"
src={logo}
alt={name}
width={64}
height={64}
/>
</Container.FlexRow>
</Button.Alternative>
</Container.Container>
);

export default WalletSelector;
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,18 @@ const Primary = (props: React.HTMLProps<HTMLButtonElement>) => (
const Alternative = (props: React.HTMLProps<HTMLButtonElement>) => (
<button
type={'button'}
className={`${props.className} py-2.5 px-5 mr-2 mb-2 text-sm font-medium text-brand focus:outline-none bg-white rounded-xl border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-200 :ring-gray-700 800 400 600 :text-white :bg-gray-700 disabled:cursor-not-allowed`}
className={`${props.className} py-2.5 px-5 mr-2 mb-2 text-sm font-medium text-brand focus:outline-none bg-white rounded-xl border border-gray-200 hover:bg-gray-100 focus:z-10 focus:ring-4 focus:ring-gray-200 :ring-gray-700 disabled:cursor-not-allowed`}
disabled={props.disabled}
onClick={props.onClick}
>
{props.children}
</button>
);

const Danger = (props: React.HTMLProps<HTMLButtonElement>) => (
<button
type={'button'}
className={`${props.className} py-2.5 px-5 mr-2 mb-2 text-sm font-medium text-white focus:outline-none bg-red-500 rounded-xl border border-gray-200 hover:bg-red-700 focus:z-10 focus:ring-4 focus:ring-red-200 :ring-red-700 disabled:cursor-not-allowed`}
disabled={props.disabled}
onClick={props.onClick}
>
Expand All @@ -47,6 +58,7 @@ const Tertiary = (props: React.HTMLProps<HTMLButtonElement>) => (
export default {
Cta,
Primary,
Danger,
Alternative,
Tertiary,
};
4 changes: 2 additions & 2 deletions src/ekoke_erc20_swap_frontend/src/js/ic/AgentContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import {
import { Principal } from '@dfinity/principal';

const CKETH_LEDGER_CANISTER_ID = 'ss2fx-dyaaa-aaaar-qacoq-cai';
const EKOKE_ERC20_SWAP_CANISTER_ID = '';
const EKOKE_LEDGER_CANISTER_ID = '';
const EKOKE_ERC20_SWAP_CANISTER_ID = 'ss2fx-dyaaa-aaaar-qacoq-cai'; // FIXME: change
const EKOKE_LEDGER_CANISTER_ID = 'ss2fx-dyaaa-aaaar-qacoq-cai'; // FIXME: change

export const ekokeErc20SwapAccount: {
owner: Principal;
Expand Down
28 changes: 28 additions & 0 deletions src/ekoke_erc20_swap_frontend/src/js/storage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { WalletProvider } from 'react-ic-wallet';

interface UserPreferences {
icWallet?: WalletProvider;
}

const getPreferences = (): UserPreferences | null => {
try {
const storedPreferences = localStorage.getItem('userPreferences');
return storedPreferences
? (JSON.parse(storedPreferences) as UserPreferences)
: null;
} catch (error) {
console.error('Failed to fetch from local storage:', error);
return null;
}
};

export const getUserIcWallet = (): WalletProvider | undefined => {
const preferences = getPreferences();
return preferences?.icWallet;
};

export const setUserIcWallet = (icWallet: WalletProvider | undefined): void => {
const preferences = getPreferences() || {};
preferences.icWallet = icWallet;
localStorage.setItem('userPreferences', JSON.stringify(preferences));
};
Loading

0 comments on commit 7a67656

Please sign in to comment.