Skip to content

Commit

Permalink
wallet-ext: qredo integration UI flow (MystenLabs#11223)
Browse files Browse the repository at this point in the history
* enables UI to show a qredo connection request and allows the user to
accept or reject the connection
* qredo connect is only enabled in dev mode (at wallet interface)



https://github.com/MystenLabs/sui/assets/10210143/8cf4f79f-256e-4d81-a40c-d8fa1a04797a



https://github.com/MystenLabs/sui/assets/10210143/ea8e780c-a4cc-4cca-b94c-841d16cfb97e


closes APPS-760
  • Loading branch information
pchrysochoidis authored May 17, 2023
1 parent 07136de commit f4211d1
Show file tree
Hide file tree
Showing 10 changed files with 415 additions and 15 deletions.
4 changes: 2 additions & 2 deletions apps/wallet/src/ui/app/components/SummaryCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function SummaryCard({
<div
className={clsx(
{ 'border border-solid border-gray-45': !noBorder },
'bg-white flex flex-col flex-nowrap rounded-2xl'
'bg-white flex flex-col flex-nowrap rounded-2xl w-full'
)}
>
{header ? (
Expand All @@ -45,7 +45,7 @@ export function SummaryCard({
) : null}
<div
className={clsx(
'flex-1 flex flex-col items-stretch flex-nowrap px-4',
'flex-1 flex flex-col items-stretch flex-nowrap px-4 overflow-y-auto',
minimalPadding ? 'py-2' : 'py-4',
showDivider
? 'divide-x-0 divide-y divide-gray-40 divide-solid'
Expand Down
5 changes: 5 additions & 0 deletions apps/wallet/src/ui/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useEffect } from 'react';
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';

import { QredoConnectInfoPage } from './pages/qredo-connect/QredoConnectInfoPage';
import { SelectQredoAccountsPage } from './pages/qredo-connect/SelectQredoAccountsPage';
import { RestrictedPage } from './pages/restricted';
import { AppType } from './redux/slices/app/AppType';
import { Staking } from './staking/home';
Expand Down Expand Up @@ -97,6 +98,10 @@ const App = () => {
path="qredo-connect/:requestID"
element={<QredoConnectInfoPage />}
/>
<Route
path="qredo-connect/:id/select"
element={<SelectQredoAccountsPage />}
/>
</Route>

<Route path="welcome" element={<WelcomePage />} />
Expand Down
4 changes: 2 additions & 2 deletions apps/wallet/src/ui/app/pages/home/nft-details/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import { hasPublicTransfer, formatAddress } from '@mysten/sui.js';
import cl from 'classnames';
import { Navigate, useSearchParams } from 'react-router-dom';

import { LabelValueItem } from './LabelValueItem';
import { LabelValuesContainer } from './LabelValuesContainer';
import { useActiveAddress } from '_app/hooks/useActiveAddress';
import { Button } from '_app/shared/ButtonUI';
import { Link } from '_app/shared/Link';
import { Collapse } from '_app/shared/collapse';
import { LabelValueItem } from '_components/LabelValueItem';
import { LabelValuesContainer } from '_components/LabelValuesContainer';
import { ExplorerLinkType } from '_components/explorer-link/ExplorerLinkType';
import Loading from '_components/loading';
import { NFTDisplayCard } from '_components/nft-display';
Expand Down
109 changes: 98 additions & 11 deletions apps/wallet/src/ui/app/pages/qredo-connect/QredoConnectInfoPage.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,114 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { useParams } from 'react-router-dom';
import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';

import { Button } from '../../shared/ButtonUI';
import { useBackgroundClient } from '../../hooks/useBackgroundClient';
import { Heading } from '../../shared/heading';
import { PageMainLayoutTitle } from '../../shared/page-main-layout/PageMainLayoutTitle';
import { Text } from '../../shared/text';
import { useQredoUIPendingRequest } from './hooks';
import { isUntrustedQredoConnect } from './utils';
import { LabelValueItem } from '_components/LabelValueItem';
import { LabelValuesContainer } from '_components/LabelValuesContainer';
import { SummaryCard } from '_components/SummaryCard';
import { UserApproveContainer } from '_components/user-approve-container';

export function QredoConnectInfoPage() {
const { requestID } = useParams();
// eslint-disable-next-line no-console
console.log('QredoConnectInfoPage', requestID);
const { data, isLoading } = useQredoUIPendingRequest(requestID);
const isUntrusted = !!data && isUntrustedQredoConnect(data);
const [isUntrustedAccepted, setIsUntrustedAccepted] = useState(false);
const navigate = useNavigate();
const backgroundService = useBackgroundClient();
useEffect(() => {
if (!isLoading && !data) {
window.close();
}
}, [isLoading, data]);
if (isLoading) {
return null;
}
if (!data) {
return null;
}
const showUntrustedWarning = isUntrusted && !isUntrustedAccepted;
return (
<>
<PageMainLayoutTitle title="Qredo Accounts Setup" />
<div className="flex flex-col flex-nowrap gap-10 justify-center flex-1 p-6 items-center">
<Text>Qredo connect is under construction.</Text>
<Button
variant="secondary"
text="Close"
onClick={() => window.close()}
<UserApproveContainer
approveTitle="Continue"
rejectTitle="Reject"
isWarning={showUntrustedWarning}
origin={data.origin}
originFavIcon={data.originFavIcon}
onSubmit={async (approved) => {
if (approved) {
if (showUntrustedWarning) {
setIsUntrustedAccepted(true);
} else {
navigate('./select', { state: { reviewed: true } });
}
} else {
await backgroundService.rejectQredoConnection({
qredoID: data.id,
});
window.close();
}
}}
addressHidden
>
<SummaryCard
header={showUntrustedWarning ? '' : 'More information'}
body={
showUntrustedWarning ? (
<div className="flex flex-col gap-2.5">
<Heading
variant="heading6"
weight="semibold"
color="gray-90"
>
Your Connection Is Not Secure
</Heading>
<Text
variant="pBodySmall"
weight="medium"
color="steel-darker"
>
If you connect your wallet with this site
your data could be exposed to attackers.
</Text>
<div className="mt-2.5">
<Text
variant="pBodySmall"
weight="medium"
color="steel-darker"
>
Click **Reject** if you don't trust this
site. Continue at your own risk.
</Text>
</div>
</div>
) : (
<LabelValuesContainer>
<LabelValueItem
label="Service"
value={data.service}
/>
<LabelValueItem
label="Token"
value={data.partialToken}
/>
<LabelValueItem
label="API URL"
value={data.apiUrl}
/>
</LabelValuesContainer>
)
}
/>
</div>
</UserApproveContainer>
</>
);
}
109 changes: 109 additions & 0 deletions apps/wallet/src/ui/app/pages/qredo-connect/SelectQredoAccountsPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { ArrowRight16 } from '@mysten/icons';
import { useEffect, useState } from 'react';
import { toast } from 'react-hot-toast';
import {
useParams,
useLocation,
Navigate,
useNavigate,
} from 'react-router-dom';

import { useBackgroundClient } from '../../hooks/useBackgroundClient';
import { Button } from '../../shared/ButtonUI';
import { SelectQredoAccountsSummaryCard } from './components/SelectQredoAccountsSummaryCard';
import { useQredoUIPendingRequest } from './hooks';
import { PasswordInputDialog } from '_components/menu/content/PasswordInputDialog';
import Overlay from '_components/overlay';
import { type Wallet } from '_src/shared/qredo-api';

export function SelectQredoAccountsPage() {
const { id } = useParams();
const { state } = useLocation();
const navigate = useNavigate();
const backgroundService = useBackgroundClient();
const qredoRequestReviewed = !!state?.reviewed;
const { data: qredoPendingRequest, isLoading: isQredoRequestLoading } =
useQredoUIPendingRequest(id);
// do not call the api if user has not clicked continue in Qredo Connect Info page
const fetchAccountsEnabled =
!isQredoRequestLoading &&
(!qredoPendingRequest || qredoRequestReviewed);

const [selectedAccounts, setSelectedAccounts] = useState<Wallet[]>([]);
const [showPassword, setShowPassword] = useState(false);
const shouldCloseWindow =
(!isQredoRequestLoading && !qredoPendingRequest) || !id;
useEffect(() => {
if (shouldCloseWindow) {
window.close();
}
}, [shouldCloseWindow]);
if (qredoPendingRequest && !qredoRequestReviewed) {
return <Navigate to="../" replace relative="path" />;
}
if (shouldCloseWindow) {
return null;
}
return (
<>
{showPassword ? (
<div className="flex flex-1 pb-4">
<PasswordInputDialog
title="Import Accounts"
continueLabel="Import"
onBackClicked={() => setShowPassword(false)}
onPasswordVerified={async (password) => {
await backgroundService.acceptQredoConnection({
qredoID: id,
accounts: selectedAccounts,
password,
});
toast.success(
`Qredo account${
selectedAccounts.length > 1 ? 's' : ''
} added`
);
navigate('/tokens?menu=/accounts');
}}
background
spacing
/>
</div>
) : (
<Overlay
showModal
title="Import Accounts"
closeOverlay={() => {
navigate(-1);
}}
>
<div className="flex flex-col flex-1 flex-nowrap align-top overflow-x-hidden overflow-y-auto gap-3">
<div className="flex flex-1 overflow-hidden">
<SelectQredoAccountsSummaryCard
fetchAccountsEnabled={fetchAccountsEnabled}
qredoID={id}
selectedAccounts={selectedAccounts}
onChange={setSelectedAccounts}
/>
</div>
<div>
<Button
size="tall"
variant="primary"
text="Continue"
after={<ArrowRight16 />}
disabled={!selectedAccounts?.length}
onClick={() => {
setShowPassword(true);
}}
/>
</div>
</div>
</Overlay>
)}
</>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

import { CheckFill16 } from '@mysten/icons';
import { formatAddress } from '@mysten/sui.js';
import cn from 'classnames';

import { type Wallet } from '_src/shared/qredo-api';
import { BadgeLabel } from '_src/ui/app/components/BadgeLabel';
import { Text } from '_src/ui/app/shared/text';

export type QredoAccountItemProps = Wallet & {
selected: boolean;
onClick: () => void;
};

export function QredoAccountItem({
selected,
address,
onClick,
labels,
}: QredoAccountItemProps) {
return (
<div
className="flex items-center flex-nowrap group gap-3 py-4 cursor-pointer"
onClick={onClick}
>
<CheckFill16
className={cn('w-4 h-4 text-gray-45 flex-shrink-0', {
'text-success': selected,
'group-hover:text-gray-60': !selected,
})}
/>
<div className="flex flex-col flex-nowrap gap-2">
<Text color={selected ? 'gray-90' : 'steel-darker'}>
{formatAddress(address)}
</Text>
{labels.length ? (
<div className="flex gap-1 flex-wrap">
{labels.map(({ key, value }) => (
<BadgeLabel key={key} label={value} />
))}
</div>
) : null}
</div>
</div>
);
}
Loading

0 comments on commit f4211d1

Please sign in to comment.