Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
13 changes: 11 additions & 2 deletions packages/gator-permissions-snap/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -257,10 +257,19 @@
"message": "Existing permissions"
},
"existingPermissionsDescription": {
"message": "You've already granted permissions for this site. Do you want to continue?"
"message": "Permissions you’ve already granted to this site"
},
"existingPermissionsConfirmButton": {
"message": "Continue"
"message": "Back to request"
},
"existingPermissionsExistingMessage": {
"message": "You have granted permissions to this site in the past."
},
"existingPermissionsSimilarMessage": {
"message": "You have granted similar permissions to this site in the past."
},
"existingPermissionsLink": {
"message": "Review them"
},
"chainLabel": {
"message": "Network"
Expand Down
35 changes: 8 additions & 27 deletions packages/gator-permissions-snap/src/core/confirmation.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,29 @@
import { UserInputEventType } from '@metamask/snaps-sdk';
import type { SnapElement } from '@metamask/snaps-sdk/jsx';
import { Button, Container, Footer } from '@metamask/snaps-sdk/jsx';

import type { DialogInterface } from './dialogInterface';
import type { UserEventDispatcher } from '../userEventDispatcher';
import type { Timeout, TimeoutFactory } from './timeoutFactory';
import type { ConfirmationProps } from './types';
import { t } from '../utils/i18n';

/**
* Dialog for handling user confirmation of permission grants.
* Manages the UI state, timeout behavior, and user interactions.
*/
export class ConfirmationDialog {
static readonly #cancelButton = 'cancel-button';
static readonly cancelButton = 'cancel-button';

static readonly #grantButton = 'grant-button';
static readonly grantButton = 'grant-button';

readonly #dialogInterface: DialogInterface;

readonly #userEventDispatcher: UserEventDispatcher;

readonly #timeoutFactory: TimeoutFactory;

#ui: SnapElement;
static isGrantDisabled = true;

#isGrantDisabled = true;
#ui: SnapElement;

#timeout: Timeout | undefined;

Expand Down Expand Up @@ -119,7 +117,7 @@ export class ConfirmationDialog {
});

const { unbind: unbindGrantButtonClick } = this.#userEventDispatcher.on({
elementName: ConfirmationDialog.#grantButton,
elementName: ConfirmationDialog.grantButton,
eventType: UserInputEventType.ButtonClickEvent,
interfaceId,
handler: async () => {
Expand All @@ -141,7 +139,7 @@ export class ConfirmationDialog {
});

const { unbind: unbindCancelButtonClick } = this.#userEventDispatcher.on({
elementName: ConfirmationDialog.#cancelButton,
elementName: ConfirmationDialog.cancelButton,
eventType: UserInputEventType.ButtonClickEvent,
interfaceId,
handler: async () => {
Expand Down Expand Up @@ -184,23 +182,7 @@ export class ConfirmationDialog {
}

#buildConfirmation(): JSX.Element {
return (
<Container>
{this.#ui}
<Footer>
<Button name={ConfirmationDialog.#cancelButton} variant="destructive">
{t('cancelButton')}
</Button>
<Button
name={ConfirmationDialog.#grantButton}
variant="primary"
disabled={this.#isGrantDisabled}
>
{t('grantButton')}
</Button>
</Footer>
</Container>
);
return this.#ui;
}

/**
Expand All @@ -217,8 +199,7 @@ export class ConfirmationDialog {
isGrantDisabled: boolean;
}): Promise<void> {
this.#ui = ui;
this.#isGrantDisabled = isGrantDisabled;

ConfirmationDialog.isGrantDisabled = isGrantDisabled;
await this.#dialogInterface.show(this.#buildConfirmation());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import {
Box,
Button,
Container,
Section,
Footer,
Heading,
Text,
Address,
Divider,
Bold,
Skeleton,
Container,
Button,
Footer,
} from '@metamask/snaps-sdk/jsx';
import type { SnapElement } from '@metamask/snaps-sdk/jsx';
import { Hex } from '@metamask/utils';

import { groupPermissionsByFromAddress } from './permissionFormatter';
Expand All @@ -23,65 +19,6 @@ import { t } from '../../utils/i18n';
export const EXISTING_PERMISSIONS_CONFIRM_BUTTON =
'existing-permissions-confirm';

// Maximum number of permissions to display per account
const MAX_PERMISSIONS_PER_ACCOUNT = 3;

/**
* Builds a skeleton loading state for the existing permissions dialog.
* Shows placeholder UI while permissions are being fetched and formatted.
*
* @param config - The configuration for the existing permissions display (used for title/description).
* @returns The skeleton loading UI as a JSX.Element.
*/
export function buildExistingPermissionsSkeletonContent(
config: ExistingPermissionDisplayConfig,
): SnapElement {
const { title, description, buttonLabel } = config;

return (
<Container>
<Box direction="vertical">
<Box center={true}>
<Heading size="lg">{t(title)}</Heading>
<Text>{t(description)}</Text>
</Box>

{/* Show 2 skeleton account groups */}
{[0, 1].map((index) => (
<Section key={`skeleton-account-${index}`}>
<Box direction="vertical">
<Box direction="horizontal" alignment="space-between">
<Text fontWeight="bold">{t('accountLabel')}</Text>
<Skeleton />
</Box>
<Divider />
{/* Show 2 skeleton permission cards */}
{[0, 1].map((permIndex) => (
<Box
key={`skeleton-permission-${permIndex}`}
direction="vertical"
>
{permIndex > 0 && <Divider />}
<Box direction="vertical">
<Skeleton />
<Skeleton />
<Skeleton />
</Box>
</Box>
))}
</Box>
</Section>
))}
</Box>
<Footer>
<Button name={EXISTING_PERMISSIONS_CONFIRM_BUTTON} disabled={true}>
{t(buttonLabel)}
</Button>
</Footer>
</Container>
);
}

/**
* Builds the existing permissions display content.
* Shows a comparison between an existing permission and what the user is about to grant.
Expand All @@ -105,42 +42,20 @@ export function buildExistingPermissionsContent(
</Box>

{Object.entries(grouped).map(([accountAddress, permissions]) => {
const displayedPermissions = permissions.slice(
0,
MAX_PERMISSIONS_PER_ACCOUNT,
);
const hasMorePermissions =
permissions.length > MAX_PERMISSIONS_PER_ACCOUNT;
const moreCount = permissions.length - MAX_PERMISSIONS_PER_ACCOUNT;

return (
<Section key={`account-${accountAddress}`}>
<Box direction="vertical">
<Box direction="horizontal" alignment="space-between">
<Text fontWeight="bold">{t('accountLabel')}</Text>
<Address address={accountAddress as Hex} displayName={true} />
</Box>
<Divider />
{displayedPermissions.map((detail, index) => (
<PermissionCard
key={`permission-${index}`}
detail={detail}
index={index}
/>
))}
{hasMorePermissions && (
<Box direction="vertical">
<Divider />
<Text>
{moreCount === 1
? t('morePermissionsCountSingle')
: t('morePermissionsCountPlural', [String(moreCount)])}
<Bold>{t('dappConnectionsLink')}</Bold>
</Text>
</Box>
)}
</Box>
</Section>
<Box key={`account-${accountAddress}`} direction="vertical">
<Section direction="horizontal" alignment="space-between">
<Text fontWeight="bold">{t('accountLabel')}</Text>
<Address address={accountAddress as Hex} displayName={true} />
</Section>
{permissions.map((detail, index) => (
<PermissionCard
key={`${accountAddress}-${index}`}
detail={detail}
index={index}
/>
))}
</Box>
);
})}
</Box>
Expand Down
Loading
Loading