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

feat: add a quick way to authorize my current account #1764

Merged
merged 22 commits into from
Jan 18, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
99cc053
feat: poc to detect authorized accounts
helciofranco Jan 8, 2025
220c3de
feat: add useCurrentAccount
helciofranco Jan 9, 2025
1827973
feat: add useCurrentTab
helciofranco Jan 9, 2025
f3996ba
feat: add useConnection
helciofranco Jan 9, 2025
fd6af5b
chore: remove unused stuff
helciofranco Jan 9, 2025
6231acd
feat: add QuickAccountConnect
helciofranco Jan 9, 2025
5947ffa
fix: prevent to check url while loading env
helciofranco Jan 10, 2025
b58627a
feat: add quick account connection to the top bar
helciofranco Jan 10, 2025
9d43642
feat: redirect user to the edit connection page
helciofranco Jan 10, 2025
ad8a460
feat: add skeleton to the quick account badge
helciofranco Jan 10, 2025
71ebf0d
docs: add changeset
helciofranco Jan 10, 2025
9974a21
feat: add DappAvatar
helciofranco Jan 10, 2025
7a0b55a
fix: prevent to display a broken favicon
helciofranco Jan 10, 2025
b2419dd
feat: display title or favicon as fallback if no connections were found
helciofranco Jan 10, 2025
2ae82b9
feat: add quick account with toast
helciofranco Jan 14, 2025
caa0e83
test: update test locators
helciofranco Jan 14, 2025
e91c345
refactor: remove unused useeffect flow
helciofranco Jan 14, 2025
3731bf1
Merge branch 'master' into hf/feat/authorized-accounts
helciofranco Jan 14, 2025
3d6eb26
fix: add missing improt
helciofranco Jan 14, 2025
adb9922
Merge branch 'master' into hf/feat/authorized-accounts
LuizAsFight Jan 18, 2025
fc61fdd
chore
LuizAsFight Jan 18, 2025
03685b9
chore
LuizAsFight Jan 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/blue-ducks-brush.md
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Icon } from '@fuel-ui/react';
import { useState } from 'react';

interface DappAvatarProps {
favIconUrl: string | undefined;
title: string | undefined;
}

export const DappAvatar = ({ favIconUrl, title }: DappAvatarProps) => {
const [imageFallback, setImageFallback] = useState(false);

if (favIconUrl && !imageFallback) {
return (
<img
src={favIconUrl}
alt="favicon"
onError={() => {
setImageFallback(true);
}}
/>
);
}

if (title) {
return <span>{title.substring(0, 1)}</span>;
}

return <Icon icon="World" color="intentsBase10" size={16} />;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { cssObj } from '@fuel-ui/css';
import { Box, ContentLoader, Tooltip } from '@fuel-ui/react';
import { useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { useCurrentTab } from '~/systems/CRX/hooks/useCurrentTab';
import { Pages } from '~/systems/Core';
import { useConnection } from '~/systems/DApp/hooks/useConnection';
import { useCurrentAccount } from '../../hooks/useCurrentAccount';
import { DappAvatar } from './DappAvatar';

enum ConnectionStatus {
CurrentAccount = 'CURRENT_ACCOUNT',
OtherAccount = 'OTHER_ACCOUNT',
NoAccounts = 'NO_ACCOUNTS',
}

export const QuickAccountConnect = () => {
const navigate = useNavigate();

const { account } = useCurrentAccount();
const { currentTab } = useCurrentTab();
const { connection } = useConnection({ url: currentTab?.url });

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 tooltip = useMemo<string>(() => {
if (status === ConnectionStatus.CurrentAccount) {
return `${account?.name} connected`;
}

if (status === ConnectionStatus.OtherAccount) {
return `${account?.name} not connected`;
}

return 'No accounts connected';
}, [status, account]);

if (!account) {
return (
<ContentLoader width={22} height={22} viewBox="0 0 22 22">
<circle cx="11" cy="11" r="11" />
</ContentLoader>
);
}

return (
<Tooltip delayDuration={0} content={tooltip}>
<Box
css={styles.root}
data-has-connection={!!connection}
onClick={() => {
if (!connection) return;
navigate(
Pages.settingsConnectedApps(undefined, {
origin: connection?.origin,
forceBackPagination: true,
})
);
}}
>
<Box css={styles.favicon}>
<DappAvatar
favIconUrl={connection?.favIconUrl || currentTab?.faviconUrl}
title={connection?.title || currentTab?.title}
/>
</Box>

<Box css={styles.badge} data-status={status}>
<Box css={styles.circle} data-status={status} />
</Box>
</Box>
</Tooltip>
);
};

const styles = {
root: cssObj({
position: 'relative',
display: 'inline-block',
width: 24,
height: 24,

'&[data-has-connection="true"]': {
cursor: 'pointer',
},
'&[data-has-connection="false"]': {
cursor: 'default',
},
}),
favicon: cssObj({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxSizing: 'border-box',

fontSize: '$xs',
backgroundColor: '$intentsBase6',
borderWidth: 1,
borderStyle: 'solid',
borderColor: 'transparent',

width: '100%',
height: '100%',
overflow: 'hidden',
borderRadius: '$full',

img: {
width: '100%',
objectFit: 'cover',
},
}),
badge: cssObj({
position: 'absolute',
zIndex: 1,

[`&[data-status="${ConnectionStatus.CurrentAccount}"]`]: {
bottom: -1,
right: -4,
},
[`&[data-status="${ConnectionStatus.OtherAccount}"]`]: {
bottom: 1,
right: -2,
},
[`&[data-status="${ConnectionStatus.NoAccounts}"]`]: {
bottom: -1,
right: -4,
},
}),
circle: cssObj({
boxSizing: 'border-box',
borderRadius: '$full',
borderStyle: 'solid',

[`&[data-status="${ConnectionStatus.CurrentAccount}"]`]: {
height: 12,
width: 12,
borderWidth: 3,
borderColor: '$bodyColor',
backgroundColor: '$intentsPrimary11',
},
[`&[data-status="${ConnectionStatus.OtherAccount}"]`]: {
width: 8,
height: 8,
backgroundColor: '$bodyColor',
borderWidth: 2,
borderColor: '$intentsPrimary11',
},
[`&[data-status="${ConnectionStatus.OtherAccount}"]:after`]: {
content: '',
position: 'absolute',
top: -2,
left: -2,
right: -2,
bottom: -2,
zIndex: -1,
borderRadius: '$full',
backgroundColor: '$bodyColor',
},
[`&[data-status="${ConnectionStatus.NoAccounts}"]`]: {
height: 12,
width: 12,
backgroundColor: '$intentsBase11',
borderWidth: 3,
borderColor: '$bodyColor',
},
}),
};
16 changes: 16 additions & 0 deletions packages/app/src/systems/Account/hooks/useCurrentAccount.ts
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,
};
}
28 changes: 28 additions & 0 deletions packages/app/src/systems/CRX/hooks/useCurrentTab.ts
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,
};
}
2 changes: 2 additions & 0 deletions packages/app/src/systems/Core/components/Layout/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { NetworkDropdown } from '~/systems/Network/components';
import { useNetworks } from '~/systems/Network/hooks';
import { useOverlay } from '~/systems/Overlay';

import { QuickAccountConnect } from '~/systems/Account/components/QuickAccountConnect/QuickAccountConnect';
import { useReportError } from '~/systems/Error';
import { useLayoutContext } from './Layout';

Expand Down Expand Up @@ -60,6 +61,7 @@ function InternalTopBar({ onBack }: TopBarProps) {
onPress={handlers.openNetworks}
/>
)}
<QuickAccountConnect />
</>
)}
</Box.Flex>
Expand Down
35 changes: 35 additions & 0 deletions packages/app/src/systems/DApp/hooks/useConnection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { Connection } from '@fuel-wallet/types';
import { useEffect, useState } from 'react';
import { ConnectionService } from '../services';

interface UseConnectionProps {
url: string | undefined;
}

const parseUrl = (url: string): string | undefined => {
try {
const { protocol, hostname, port } = new URL(url);
return `${protocol}//${hostname}${port ? `:${port}` : ''}`;
} catch (_e) {
return undefined;
}
};

export const useConnection = ({ url }: UseConnectionProps) => {
const [connection, setConnection] = useState<Connection | undefined>();

useEffect(() => {
const fetchConnection = async () => {
if (!url) return;
const origin = parseUrl(url);
const existingConnection = await ConnectionService.getConnection(origin);
setConnection(existingConnection);
};

fetchConnection();
}, [url]);

return {
connection,
};
};
11 changes: 10 additions & 1 deletion packages/app/src/systems/Settings/hooks/useConnections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ const selectors = {

export function useConnections() {
const [searchQuery, setSearchQuery] = useSearchParams();

const service = useInterpret(() =>
connectionsMachine
.withContext({
Expand Down Expand Up @@ -128,17 +129,24 @@ export function useConnections() {
function search(text: string) {
service.send({ type: 'SEARCH', input: text });
}

function clearSearch() {
service.send('CLEAR_SEARCH');
}

function editConnection(connection: Connection) {
service.send({ type: 'EDIT_CONNECTION', input: connection });
}

function removeConnection(connection: Connection) {
service.send({ type: 'REMOVE_CONNECTION', input: connection });
}

function cancel() {
if (screen === ConnectionScreen.edit) {
if (
screen === ConnectionScreen.edit &&
!searchQuery.get('forceBackPagination')
) {
service.send('CANCEL');
} else {
navigate(-1);
Expand All @@ -148,6 +156,7 @@ export function useConnections() {
function isConnected(account: string) {
return connectedAccounts.some((a) => a?.address === account);
}

function toggleAccount(account: string, isConnected?: boolean) {
service.send({
type: isConnected ? 'REMOVE_ACCOUNT' : 'ADD_ACCOUNT',
Expand Down
Loading