-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
connected wallet dialogs and enable/disable (#689)
* table cells to new components * stub dialoges * wire up revoke dialog stub * ui for revoke wallet dialog * style revoke dialog title area * working connect dialog * homogenize wallet table cell props to greatly simplify table row component * explain in code comment * xyo and provider logos * decompose into small components * track enabled state for all wallets * enabled-only wallets, expose wallets as getter, rename interface * more rename and make persistence configurable * configure via class property instead of constructor * even better naming and code comments * support ignoring connect explanation dialog * swap revoke button for info icon * toggle for ignoring connect dialog * more generic component name * better default localStorage key * missing image.d.ts
- Loading branch information
Showing
37 changed files
with
655 additions
and
117 deletions.
There are no files selected for viewing
128 changes: 128 additions & 0 deletions
128
packages/sdk/packages/connected-accounts/src/classes/EnabledWallets.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import { DiscoveredWallets, EIP6963Connector } from '@xylabs/react-crypto' | ||
|
||
const DEFAULT_LOCAL_STORAGE_KEY = 'XYO|EnabledWallets' | ||
|
||
/** | ||
* State for storing wallets and their enabled/disabled status by name | ||
*/ | ||
export interface EnabledEthWalletsState { | ||
[rdns: string]: { | ||
enabled: boolean | ||
wallet: EIP6963Connector | ||
} | ||
} | ||
|
||
/** | ||
* State for storing only enabled/disabled status of a wallet by name | ||
*/ | ||
export interface EnabledWalletsSavedState { | ||
[rdns: string]: boolean | ||
} | ||
|
||
export type WalletListener = () => void | ||
|
||
export class EnabledEthWalletConnections { | ||
// control whether or not enabled/disabled preferences are persisted (i.e. in localStorage) | ||
public persistPreferences = true | ||
|
||
// Map of wallet names and their enabled/disabled state | ||
private enabledWallets: EnabledWalletsSavedState = {} | ||
|
||
// Map of wallet names, their enabled/disabled state, and their wallet class | ||
private ethWalletsState: EnabledEthWalletsState = {} | ||
|
||
// list of listeners that want to be notified on wallet changes | ||
private listeners: WalletListener[] = [] | ||
|
||
// key to use in localStorage when persisting preferences | ||
private localStorageKey = DEFAULT_LOCAL_STORAGE_KEY | ||
|
||
constructor(localStorageKey = DEFAULT_LOCAL_STORAGE_KEY) { | ||
this.localStorageKey = localStorageKey | ||
this.reviveSettings() | ||
} | ||
|
||
get wallets() { | ||
return this.ethWalletsState | ||
} | ||
|
||
disableWallet(rdns: string) { | ||
this.toggleEnabledWallet(rdns, false) | ||
} | ||
|
||
enableWallet(rdns: string) { | ||
this.toggleEnabledWallet(rdns, true) | ||
} | ||
|
||
/** | ||
* Given a new set of wallets, set their enabled state based off previous preferences | ||
*/ | ||
resetWallets(wallets: DiscoveredWallets) { | ||
const newWallets: EnabledEthWalletsState = {} | ||
|
||
const addWallet = ([walletName, wallet]: [string, EIP6963Connector]) => { | ||
newWallets[walletName] = { | ||
// preserve the existing enabled state | ||
enabled: walletName in this.enabledWallets ? this.enabledWallets[walletName] : true, | ||
wallet, | ||
} | ||
} | ||
|
||
Object.entries(wallets).forEach(addWallet.bind(this)) | ||
this.ethWalletsState = newWallets | ||
this.emitChange() | ||
} | ||
|
||
subscribe(listener: WalletListener) { | ||
this.listeners = [...this.listeners, listener] | ||
return () => { | ||
this.listeners = this.listeners.filter((existingListener) => existingListener !== listener) | ||
} | ||
} | ||
|
||
toggleEnabledWallet(rdns: string, enabled: boolean) { | ||
if (rdns && this.ethWalletsState[rdns]) { | ||
this.ethWalletsState[rdns].enabled = enabled | ||
this.ethWalletsState = { ...this.ethWalletsState } | ||
this.emitChange() | ||
} | ||
} | ||
|
||
private emitChange() { | ||
for (const listener of this.listeners) { | ||
listener() | ||
} | ||
|
||
this.persistSettings() | ||
} | ||
|
||
private isPersistance(method: () => void) { | ||
if (this.persistPreferences) { | ||
method() | ||
} | ||
} | ||
|
||
private persistSettings() { | ||
this.isPersistance(() => { | ||
// convert wallet enabled selections into serializable state | ||
const enabledWallets = Object.entries(this.ethWalletsState).reduce((acc, [rdns, { enabled }]) => { | ||
acc[rdns] = enabled | ||
return acc | ||
}, {} as EnabledWalletsSavedState) | ||
|
||
localStorage.setItem(this.localStorageKey, JSON.stringify(enabledWallets)) | ||
}) | ||
} | ||
|
||
private reviveSettings() { | ||
this.isPersistance(() => { | ||
const existingEntries = localStorage.getItem(this.localStorageKey) | ||
try { | ||
const entries = existingEntries ? JSON.parse(existingEntries) : {} | ||
this.enabledWallets = entries | ||
} catch (e) { | ||
console.warn(`Error parsing saved enabled wallet entries: ${(e as Error).message}`) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './EnabledWallets' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 28 additions & 17 deletions
45
packages/sdk/packages/connected-accounts/src/components/ConnectedAccountsFlexbox.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,27 +1,38 @@ | ||
import { Typography, useTheme } from '@mui/material' | ||
import { FlexBoxProps, FlexCol } from '@xylabs/react-flexbox' | ||
import { forwardRef } from 'react' | ||
|
||
import { useDetectedWallets } from '../hooks' | ||
import { ConnectedWalletsTable } from './wallet' | ||
|
||
export const ConnectedAccountsFlexbox: React.FC<FlexBoxProps> = (props) => { | ||
const theme = useTheme() | ||
export interface ConnectedAccountsFlexboxProps extends FlexBoxProps { | ||
ignoreConnectDialog?: boolean | ||
// A callback that is invoked when the option to ignore the dialog is checked | ||
onIgnoreConnectDialog?: (checked: boolean) => void | ||
} | ||
|
||
export const ConnectedAccountsFlexbox = forwardRef<HTMLDivElement, ConnectedAccountsFlexboxProps>( | ||
({ ignoreConnectDialog, onIgnoreConnectDialog, ...props }, ref) => { | ||
const theme = useTheme() | ||
|
||
const { totalConnectedAccounts, sortedWallets } = useDetectedWallets() | ||
const { totalConnectedAccounts, sortedWallets } = useDetectedWallets() | ||
|
||
return ( | ||
<FlexCol alignItems="stretch" justifyContent="start" gap={2} {...props}> | ||
<FlexCol alignItems="start"> | ||
<Typography variant={'h2'} sx={{ mb: 0.5 }}> | ||
Detected Web3 Wallets | ||
</Typography> | ||
{totalConnectedAccounts ? ( | ||
<Typography variant={'subtitle1'} color={theme.palette.secondary.main} sx={{ opacity: 0.5 }}> | ||
Total Connected Accounts: {totalConnectedAccounts} | ||
return ( | ||
<FlexCol alignItems="stretch" justifyContent="start" gap={2} ref={ref} {...props}> | ||
<FlexCol alignItems="start"> | ||
<Typography variant={'h2'} sx={{ mb: 0.5 }}> | ||
Detected Web3 Wallets | ||
</Typography> | ||
) : null} | ||
{totalConnectedAccounts ? ( | ||
<Typography variant={'subtitle1'} color={theme.palette.secondary.main} sx={{ opacity: 0.5 }}> | ||
Total Connected Accounts: {totalConnectedAccounts} | ||
</Typography> | ||
) : null} | ||
</FlexCol> | ||
<ConnectedWalletsTable wallets={sortedWallets} ignoreConnectDialog={ignoreConnectDialog} onIgnoreConnectDialog={onIgnoreConnectDialog} /> | ||
</FlexCol> | ||
<ConnectedWalletsTable wallets={sortedWallets} /> | ||
</FlexCol> | ||
) | ||
} | ||
) | ||
}, | ||
) | ||
|
||
ConnectedAccountsFlexbox.displayName = 'ConnectedAccountsFlexbox' |
16 changes: 16 additions & 0 deletions
16
...packages/connected-accounts/src/components/wallet/dialogs/connect/CheckboxFormControl.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { Checkbox, FormControl, FormControlProps, FormLabel } from '@mui/material' | ||
|
||
export interface CheckboxFormControlProps extends FormControlProps { | ||
onCheckChanged?: (checked: boolean) => void | ||
} | ||
|
||
export const CheckboxFormControl: React.FC<CheckboxFormControlProps> = ({ onCheckChanged, ...props }) => { | ||
return ( | ||
<FormControl {...props}> | ||
<FormLabel> | ||
<Checkbox onChange={(_, checked) => onCheckChanged?.(checked)} /> | ||
Do not show this again. | ||
</FormLabel> | ||
</FormControl> | ||
) | ||
} |
43 changes: 43 additions & 0 deletions
43
packages/sdk/packages/connected-accounts/src/components/wallet/dialogs/connect/Dialog.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { Button, Dialog, DialogActions, DialogContent, DialogProps, DialogTitle } from '@mui/material' | ||
|
||
import { ActiveProvider } from '../../lib' | ||
import { CheckboxFormControl } from './CheckboxFormControl' | ||
import { LinkedProvidersFlexbox } from './LinkedProvidersFlexbox' | ||
import { WalletPermissionsFlexbox } from './Permissions' | ||
|
||
export interface ConnectWalletDialogProps extends DialogProps { | ||
activeProvider?: ActiveProvider | ||
onIgnoreConnectDialog?: (checked: boolean) => void | ||
} | ||
|
||
export const ConnectWalletDialog: React.FC<ConnectWalletDialogProps> = ({ activeProvider, onIgnoreConnectDialog, ...props }) => { | ||
const { icon, providerName } = activeProvider ?? {} | ||
|
||
const onConnect = async () => { | ||
try { | ||
await activeProvider?.connectWallet?.() | ||
props.onClose?.({}, 'escapeKeyDown') | ||
} catch (e) { | ||
console.warn(`Error connecting to wallet: ${(e as Error).message}`) | ||
} | ||
} | ||
|
||
return ( | ||
<Dialog PaperProps={{ sx: { display: 'flex', gap: 4 } }} {...props}> | ||
<DialogTitle sx={{ textAlign: 'center' }}>XYO Wants To Access The Blockchain on Your Behalf</DialogTitle> | ||
<DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}> | ||
<LinkedProvidersFlexbox icon={icon} providerName={providerName} /> | ||
<WalletPermissionsFlexbox /> | ||
<CheckboxFormControl onCheckChanged={onIgnoreConnectDialog} /> | ||
</DialogContent> | ||
<DialogActions> | ||
<Button variant="outlined" onClick={() => props.onClose?.({}, 'escapeKeyDown')}> | ||
Close | ||
</Button> | ||
<Button variant="contained" onClick={onConnect}> | ||
Connect | ||
</Button> | ||
</DialogActions> | ||
</Dialog> | ||
) | ||
} |
27 changes: 27 additions & 0 deletions
27
...kages/connected-accounts/src/components/wallet/dialogs/connect/LinkedProvidersFlexbox.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { SyncAlt } from '@mui/icons-material' | ||
import { Typography } from '@mui/material' | ||
import { ConstrainedImage } from '@xylabs/react-crypto' | ||
import { FlexBoxProps, FlexCol, FlexRow } from '@xylabs/react-flexbox' | ||
|
||
import { xyoColorLogo } from '../../../../img' | ||
|
||
export interface LinkedProvidersFlexboxProps extends FlexBoxProps { | ||
icon?: string | ||
providerName?: string | ||
} | ||
|
||
export const LinkedProvidersFlexbox: React.FC<LinkedProvidersFlexboxProps> = ({ icon, providerName, ...props }) => { | ||
return ( | ||
<FlexRow gap={4} justifyContent="space-evenly" {...props}> | ||
<FlexCol gap={0.5}> | ||
<img alt="XYO Logo" src={xyoColorLogo} style={{ height: '48px' }} /> | ||
<Typography variant="subtitle1">XYO App</Typography> | ||
</FlexCol> | ||
<SyncAlt fontSize={'large'} /> | ||
<FlexCol gap={0.5}> | ||
<ConstrainedImage constrainedValue={'48px'} src={icon} alt={providerName} style={{ height: '48px', maxWidth: '48px' }} /> | ||
<Typography variant="subtitle1">{providerName}</Typography> | ||
</FlexCol> | ||
</FlexRow> | ||
) | ||
} |
30 changes: 30 additions & 0 deletions
30
...ges/sdk/packages/connected-accounts/src/components/wallet/dialogs/connect/Permissions.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { Link, Typography } from '@mui/material' | ||
import { FlexBoxProps, FlexCol } from '@xylabs/react-flexbox' | ||
|
||
export interface WalletPermissionsFlexBoxProps extends FlexBoxProps {} | ||
|
||
export const WalletPermissionsFlexbox: React.FC<WalletPermissionsFlexBoxProps> = (props) => { | ||
return ( | ||
<FlexCol gap={4} {...props}> | ||
<Typography fontWeight="bold" sx={{ textAlign: 'center' }}> | ||
This will allow XYO to: | ||
</Typography> | ||
<ul> | ||
<li>View your wallet account(s) and address(es)</li> | ||
<li>Read-only access to browse the public blockchain(s) you select</li> | ||
</ul> | ||
<Typography variant="subtitle1" sx={{ textAlign: 'center' }}> | ||
You control what accounts to share and what blockchains to view. You can see or revoke access via your wallet's settings at anytime. View | ||
more on XYO's sovereign data philosophy{' '} | ||
<Link | ||
href="https://cointelegraph.com/innovation-circle/decentralization-and-sovereignty-debunking-our-approach-to-digital-sovereignty" | ||
sx={{ fontWeight: 'bold' }} | ||
target="_blank" | ||
> | ||
here | ||
</Link> | ||
. | ||
</Typography> | ||
</FlexCol> | ||
) | ||
} |
2 changes: 2 additions & 0 deletions
2
packages/sdk/packages/connected-accounts/src/components/wallet/dialogs/connect/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './CheckboxFormControl' | ||
export * from './Dialog' |
2 changes: 2 additions & 0 deletions
2
packages/sdk/packages/connected-accounts/src/components/wallet/dialogs/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './connect' | ||
export * from './revoke' |
32 changes: 32 additions & 0 deletions
32
packages/sdk/packages/connected-accounts/src/components/wallet/dialogs/revoke/Dialog.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { Button, Dialog, DialogActions, DialogContent, DialogProps, DialogTitle, Typography } from '@mui/material' | ||
import { ConstrainedImage } from '@xylabs/react-crypto' | ||
import { FlexRow } from '@xylabs/react-flexbox' | ||
|
||
import { ActiveProvider } from '../../lib' | ||
|
||
export interface RevokeWalletConnectionDialogProps extends DialogProps { | ||
activeProvider?: ActiveProvider | ||
} | ||
|
||
export const RevokeWalletConnectionDialog: React.FC<RevokeWalletConnectionDialogProps> = ({ activeProvider, ...props }) => { | ||
return ( | ||
<Dialog {...props}> | ||
<FlexRow gap={2} justifyContent="start" pl={2}> | ||
<ConstrainedImage src={activeProvider?.icon} constrainedValue={'24px'} /> | ||
<DialogTitle sx={{ pl: 0 }}>Revoke {activeProvider?.providerName} Access</DialogTitle> | ||
</FlexRow> | ||
<DialogContent> | ||
<Typography> | ||
Revoking access to your wallet must be done from the wallet's browser extension. Wallets grant access to specific domains please | ||
consult {activeProvider?.providerName}'s documentation on how to revoke access to this website: | ||
</Typography> | ||
<Typography>{window.location.origin}</Typography> | ||
</DialogContent> | ||
<DialogActions> | ||
<Button variant="contained" onClick={() => props.onClose?.({}, 'escapeKeyDown')}> | ||
Close | ||
</Button> | ||
</DialogActions> | ||
</Dialog> | ||
) | ||
} |
1 change: 1 addition & 0 deletions
1
packages/sdk/packages/connected-accounts/src/components/wallet/dialogs/revoke/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './Dialog' |
1 change: 1 addition & 0 deletions
1
packages/sdk/packages/connected-accounts/src/components/wallet/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './dialogs' | ||
export * from './table' |
7 changes: 7 additions & 0 deletions
7
packages/sdk/packages/connected-accounts/src/components/wallet/lib/ActiveProvider.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { EthWallet } from '@xylabs/react-crypto' | ||
|
||
export interface ActiveProvider { | ||
connectWallet?: EthWallet['connectWallet'] | ||
icon?: string | ||
providerName?: string | ||
} |
Oops, something went wrong.