-
Notifications
You must be signed in to change notification settings - Fork 447
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add a quick way to authorize my current account (#1764)
- Closes #1719 - Closes `FE-1174` --- | 📷 Demo | | --- | | <video src="https://github.com/user-attachments/assets/31acded5-4781-43e9-bab1-e6e8f636d3bb" /> |
- Loading branch information
1 parent
2460bee
commit d81ee48
Showing
9 changed files
with
318 additions
and
18 deletions.
There are no files selected for viewing
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,5 @@ | ||
--- | ||
"fuels-wallet": minor | ||
--- | ||
|
||
Add current account connection status to the header |
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
144 changes: 144 additions & 0 deletions
144
packages/app/src/systems/Account/components/QuickAccountConnect/QuickAccountConnect.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,144 @@ | ||
import { cssObj } from '@fuel-ui/css'; | ||
import { | ||
Alert, | ||
Avatar, | ||
Box, | ||
Icon, | ||
IconButton, | ||
Text, | ||
VStack, | ||
toast, | ||
} from '@fuel-ui/react'; | ||
import { useEffect, useMemo, useState } from 'react'; | ||
import { useCurrentTab } from '~/systems/CRX/hooks/useCurrentTab'; | ||
import { useConnection } from '~/systems/DApp/hooks/useConnection'; | ||
import { useOrigin } from '~/systems/DApp/hooks/useOrigin'; | ||
import { ConnectionService } from '~/systems/DApp/services'; | ||
import { useCurrentAccount } from '../../hooks/useCurrentAccount'; | ||
|
||
enum ConnectionStatus { | ||
CurrentAccount = 'CURRENT_ACCOUNT', | ||
OtherAccount = 'OTHER_ACCOUNT', | ||
NoAccounts = 'NO_ACCOUNTS', | ||
} | ||
|
||
export const getDismissKey = (account: string, origin: string) => { | ||
return `quick-account-connect-${account}-${origin}`; | ||
}; | ||
|
||
export const QuickAccountConnect = () => { | ||
const { account } = useCurrentAccount(); | ||
const { currentTab } = useCurrentTab(); | ||
const origin = useOrigin({ url: currentTab?.url }); | ||
const { connection, fetchConnection } = useConnection({ | ||
origin: origin?.full, | ||
}); | ||
|
||
const [dismissed, setDismissed] = useState(true); | ||
|
||
const status = useMemo<ConnectionStatus>(() => { | ||
if (!account || !connection) { | ||
return ConnectionStatus.NoAccounts; | ||
} | ||
|
||
if (connection.accounts.includes(account.address)) { | ||
return ConnectionStatus.CurrentAccount; | ||
} | ||
|
||
return ConnectionStatus.OtherAccount; | ||
}, [account, connection]); | ||
|
||
const onConnect = async () => { | ||
if (!origin || !account) return; | ||
await ConnectionService.addAccountTo({ | ||
origin: origin.full, | ||
account: account.address, | ||
}); | ||
await fetchConnection(); | ||
toast.success(`${account?.name} connected`); | ||
}; | ||
|
||
const onDismiss = () => { | ||
if (!origin || !account) return; | ||
setDismissed(true); | ||
localStorage.setItem(getDismissKey(account.address, origin.full), 'true'); | ||
}; | ||
|
||
useEffect(() => { | ||
if (!origin || !account) return; | ||
const hasDismissed = localStorage.getItem( | ||
getDismissKey(account.address, origin.full) | ||
); | ||
setDismissed(!!hasDismissed); | ||
}, [account, origin]); | ||
|
||
return ( | ||
<Box | ||
css={styles.wrapper} | ||
data-open={status === ConnectionStatus.OtherAccount && !dismissed} | ||
> | ||
<Alert status="info" css={styles.alert}> | ||
<Alert.Description as="div"> | ||
<Avatar.Generated | ||
size="sm" | ||
hash={account?.address as string} | ||
css={{ boxShadow: '$sm', flexShrink: 0 }} | ||
/> | ||
|
||
<VStack gap="$1"> | ||
<span> | ||
{account?.name || 'This account'} isn't connected to{' '} | ||
{origin?.short || 'this app'} | ||
</span> | ||
|
||
<Text color="scalesBlue10" css={styles.cta} onClick={onConnect}> | ||
Connect account | ||
</Text> | ||
</VStack> | ||
|
||
<IconButton | ||
icon={Icon.is('X')} | ||
aria-label="Close" | ||
variant="link" | ||
onPress={onDismiss} | ||
/> | ||
</Alert.Description> | ||
</Alert> | ||
</Box> | ||
); | ||
}; | ||
|
||
const styles = { | ||
wrapper: cssObj({ | ||
position: 'fixed', | ||
paddingLeft: '$4', | ||
paddingRight: '$4', | ||
paddingBottom: '$4', | ||
bottom: 0, | ||
zIndex: '$2', | ||
opacity: 0, | ||
transition: 'opacity 0.2s ease-in-out', | ||
pointerEvents: 'none', | ||
|
||
'&[data-open="true"]': { | ||
opacity: 1, | ||
pointerEvents: 'auto', | ||
}, | ||
}), | ||
alert: cssObj({ | ||
'& .fuel_Alert-icon': { | ||
display: 'none', | ||
}, | ||
'& .fuel_Alert-description': { | ||
display: 'flex', | ||
gap: '$2', | ||
alignItems: 'flex-start', | ||
}, | ||
}), | ||
cta: cssObj({ | ||
'&:hover': { | ||
cursor: 'pointer', | ||
textDecoration: 'underline', | ||
}, | ||
}), | ||
}; |
16 changes: 16 additions & 0 deletions
16
packages/app/src/systems/Account/hooks/useCurrentAccount.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,16 @@ | ||
import { Services, store } from '~/store'; | ||
import type { AccountsMachineState } from '../machines'; | ||
|
||
const selectors = { | ||
account(state: AccountsMachineState) { | ||
return state.context.account; | ||
}, | ||
}; | ||
|
||
export function useCurrentAccount() { | ||
const account = store.useSelector(Services.accounts, selectors.account); | ||
|
||
return { | ||
account, | ||
}; | ||
} |
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,28 @@ | ||
import { useEffect, useState } from 'react'; | ||
|
||
interface CurrentTab { | ||
url: string | undefined; | ||
title: string | undefined; | ||
faviconUrl: string | undefined; | ||
} | ||
|
||
export function useCurrentTab() { | ||
const [currentTab, setCurrentTab] = useState<CurrentTab | undefined>(); | ||
|
||
useEffect(() => { | ||
if (!chrome?.tabs) return; | ||
|
||
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => { | ||
const currentTab = tabs[0]; | ||
setCurrentTab({ | ||
url: currentTab?.url, | ||
title: currentTab?.title, | ||
faviconUrl: currentTab?.favIconUrl, | ||
}); | ||
}); | ||
}, []); | ||
|
||
return { | ||
currentTab, | ||
}; | ||
} |
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,26 @@ | ||
import type { Connection } from '@fuel-wallet/types'; | ||
import { useCallback, useEffect, useState } from 'react'; | ||
import { ConnectionService } from '../services'; | ||
|
||
interface UseConnectionProps { | ||
origin: string | undefined; | ||
} | ||
|
||
export const useConnection = ({ origin }: UseConnectionProps) => { | ||
const [connection, setConnection] = useState<Connection | undefined>(); | ||
|
||
const fetchConnection = useCallback(async () => { | ||
if (!origin) return; | ||
const existingConnection = await ConnectionService.getConnection(origin); | ||
setConnection(existingConnection); | ||
}, [origin]); | ||
|
||
useEffect(() => { | ||
fetchConnection(); | ||
}, [fetchConnection]); | ||
|
||
return { | ||
connection, | ||
fetchConnection, | ||
}; | ||
}; |
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 { useMemo } from 'react'; | ||
|
||
interface UseOriginProps { | ||
url: string | undefined; | ||
} | ||
|
||
interface Origin { | ||
short: string; | ||
full: string; | ||
} | ||
|
||
const parseUrl = (url: string): Origin | undefined => { | ||
try { | ||
const { protocol, hostname, port } = new URL(url); | ||
const short = `${hostname}${port ? `:${port}` : ''}`; | ||
return { | ||
short, | ||
full: `${protocol}//${short}`, | ||
}; | ||
} catch (_e) { | ||
return undefined; | ||
} | ||
}; | ||
|
||
export const useOrigin = ({ url }: UseOriginProps) => { | ||
return useMemo(() => (url ? parseUrl(url) : undefined), [url]); | ||
}; |
Oops, something went wrong.