Skip to content

Commit fe0e8e7

Browse files
feat(frontend): handle WC connect in ScannerCode
1 parent 1b468de commit fe0e8e7

File tree

6 files changed

+195
-84
lines changed

6 files changed

+195
-84
lines changed

src/frontend/src/lib/components/scanner/ScannerCode.svelte

Lines changed: 60 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@
77
import { feeRatePercentilesStore } from '$btc/stores/fee-rate-percentiles.store';
88
import { OCP_PAY_WITH_BTC_ENABLED } from '$env/open-crypto-pay.env';
99
import IconChain from '$lib/components/icons/IconChain.svelte';
10+
import IconHelpCircle from '$lib/components/icons/IconHelpCircle.svelte';
1011
import QrCodeScanner from '$lib/components/qr/QrCodeScanner.svelte';
1112
import ScannerCodeInput from '$lib/components/scanner/ScannerCodeInput.svelte';
1213
import BottomSheet from '$lib/components/ui/BottomSheet.svelte';
1314
import Button from '$lib/components/ui/Button.svelte';
14-
import ButtonGroup from '$lib/components/ui/ButtonGroup.svelte';
15-
import ContentWithToolbar from '$lib/components/ui/ContentWithToolbar.svelte';
1615
import Responsive from '$lib/components/ui/Responsive.svelte';
1716
import { OPEN_CRYPTO_PAY_ENTER_MANUALLY_BUTTON } from '$lib/constants/test-ids.constants';
1817
import { btcAddressMainnet } from '$lib/derived/address.derived';
@@ -27,14 +26,18 @@
2726
import { PAY_CONTEXT_KEY, type PayContext } from '$lib/stores/open-crypto-pay.store';
2827
import type { QrStatus } from '$lib/types/qr-code';
2928
import { ScannerResults } from '$lib/types/scanner';
29+
import { replaceOisyPlaceholders } from '$lib/utils/i18n.utils';
3030
import { prepareBasePayableTokens } from '$lib/utils/open-crypto-pay.utils';
3131
import { waitReady } from '$lib/utils/timeout.utils';
3232
3333
interface Props {
34-
onNext: (results: ScannerResults) => void;
34+
onNext: (params: { results: ScannerResults; code?: string }) => void;
35+
onOpenInfo?: () => void;
3536
}
3637
37-
let { onNext }: Props = $props();
38+
let { onNext, onOpenInfo }: Props = $props();
39+
40+
const WALLET_CONNECT_URI_PREFIX = 'wc:';
3841
3942
let openBottomSheet = $state(false);
4043
let uri = $state('');
@@ -44,6 +47,11 @@
4447
const { setData, setAvailableTokens } = getContext<PayContext>(PAY_CONTEXT_KEY);
4548
4649
const processCode = async (code: string) => {
50+
if (code.startsWith(WALLET_CONNECT_URI_PREFIX)) {
51+
onNext({ results: ScannerResults.WALLET_CONNECT, code });
52+
return;
53+
}
54+
4755
busy.start();
4856
4957
error = '';
@@ -74,7 +82,7 @@
7482
7583
setAvailableTokens(tokensWithFees);
7684
77-
onNext(ScannerResults.PAY);
85+
onNext({ results: ScannerResults.PAY });
7886
} catch (_: unknown) {
7987
error = $i18n.scanner.error.code_link_is_not_valid;
8088
} finally {
@@ -101,67 +109,67 @@
101109
});
102110
</script>
103111

104-
<ContentWithToolbar styleClass="flex flex-col gap-3 md:gap-4 w-full">
105-
<QrCodeScanner onScan={handleScan} />
112+
<div class="relative flex w-full flex-col bg-tertiary">
113+
<QrCodeScanner expandedLayout onScan={handleScan} />
106114

107115
<Responsive up="md">
108116
<ScannerCodeInput
109117
name="uri"
110118
{error}
111119
label={$i18n.scanner.text.url_or_code}
112120
placeholder={$i18n.scanner.text.enter_or_paste_code}
121+
styleClass="absolute right-0 bottom-[90px] left-0 mx-auto w-[90%] rounded-lg bg-surface"
113122
bind:value={uri}
114-
/>
123+
>
124+
<Button disabled={isEmptyUri} fullWidth onclick={handleManualConnect} paddingSmall>
125+
{$i18n.core.text.continue}
126+
</Button>
127+
</ScannerCodeInput>
115128
</Responsive>
116129

117130
<Responsive down="sm">
118131
<BottomSheet contentClass="min-h-[10vh]" bind:visible={openBottomSheet}>
119132
{#snippet content()}
120-
<div class="mb-4">
121-
<ScannerCodeInput
122-
name="uri"
123-
{error}
124-
label={$i18n.scanner.text.url_or_code}
125-
placeholder={$i18n.scanner.text.enter_or_paste_code}
126-
bind:value={uri}
127-
/>
128-
</div>
129-
{/snippet}
130-
131-
{#snippet footer()}
132-
<Button disabled={isEmptyUri} fullWidth onclick={handleManualConnect}>
133-
{$i18n.core.text.continue}
134-
</Button>
133+
<ScannerCodeInput
134+
name="uri"
135+
{error}
136+
label={$i18n.scanner.text.url_or_code}
137+
placeholder={$i18n.scanner.text.enter_or_paste_code}
138+
bind:value={uri}
139+
>
140+
<Button disabled={isEmptyUri} fullWidth onclick={handleManualConnect} paddingSmall>
141+
{$i18n.core.text.continue}
142+
</Button>
143+
</ScannerCodeInput>
135144
{/snippet}
136145
</BottomSheet>
146+
147+
<div class="absolute right-0 bottom-[90px] left-0 mx-auto flex w-[200px] justify-center">
148+
<Button
149+
colorStyle="tertiary"
150+
innerStyleClass="flex items-center justify-center"
151+
onclick={() => {
152+
uri = '';
153+
error = '';
154+
openBottomSheet = true;
155+
}}
156+
testId={OPEN_CRYPTO_PAY_ENTER_MANUALLY_BUTTON}
157+
>
158+
{$i18n.scanner.text.enter_manually}
159+
160+
<IconChain />
161+
</Button>
162+
</div>
137163
</Responsive>
138164

139-
{#snippet toolbar()}
140-
<Responsive up="md">
141-
<ButtonGroup>
142-
<Button disabled={isEmptyUri} onclick={handleManualConnect}>
143-
{$i18n.core.text.continue}
144-
</Button>
145-
</ButtonGroup>
146-
</Responsive>
147-
148-
<Responsive down="sm">
149-
<div class="mb-4 flex flex-0">
150-
<Button
151-
colorStyle="primary"
152-
innerStyleClass="flex items-center justify-center"
153-
link
154-
onclick={() => {
155-
uri = '';
156-
error = '';
157-
openBottomSheet = true;
158-
}}
159-
testId={OPEN_CRYPTO_PAY_ENTER_MANUALLY_BUTTON}
160-
>
161-
{$i18n.scanner.text.enter_manually}
162-
<IconChain />
163-
</Button>
164-
</div>
165-
</Responsive>
166-
{/snippet}
167-
</ContentWithToolbar>
165+
<Button
166+
fullWidth
167+
link
168+
onclick={onOpenInfo}
169+
styleClass="text-secondary bg-surface py-4 rounded-none"
170+
>
171+
<span>{replaceOisyPlaceholders($i18n.scanner.text.what_is_scan)}</span>
172+
173+
<IconHelpCircle />
174+
</Button>
175+
</div>

src/frontend/src/lib/components/scanner/ScannerModal.svelte

Lines changed: 76 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
<script lang="ts">
22
import { WizardModal, type WizardStep, type WizardSteps } from '@dfinity/gix-components';
3-
import { assertNever, isNullish } from '@dfinity/utils';
4-
import { setContext } from 'svelte';
3+
import { assertNever, isNullish, nonNullish } from '@dfinity/utils';
4+
import { setContext, untrack } from 'svelte';
55
import OpenCryptoPayWizard from '$lib/components/open-crypto-pay/OpenCryptoPayWizard.svelte';
66
import ScannerCode from '$lib/components/scanner/ScannerCode.svelte';
7+
import ScannerInfo from '$lib/components/scanner/ScannerInfo.svelte';
78
import ScannerModalPayDataLoader from '$lib/components/scanner/ScannerModalPayDataLoader.svelte';
89
import WalletConnectSessionWizard from '$lib/components/wallet-connect/WalletConnectSessionWizard.svelte';
910
import { scannerWizardSteps } from '$lib/config/scanner.config';
11+
import { modalUniversalScannerData } from '$lib/derived/modal.derived';
1012
import { PLAUSIBLE_EVENTS } from '$lib/enums/plausible';
1113
import { ProgressStepsPayment } from '$lib/enums/progress-steps';
1214
import { WizardStepsScanner } from '$lib/enums/wizard-steps';
1315
import { trackEvent } from '$lib/services/analytics.services';
16+
import { connectListener } from '$lib/services/wallet-connect.services';
1417
import { i18n } from '$lib/stores/i18n.store';
1518
import { modalStore } from '$lib/stores/modal.store';
1619
import {
@@ -63,47 +66,93 @@
6366
});
6467
};
6568
66-
const onWalletConnectConnect = async () => {
67-
// TODO: implement this function
69+
const goToScanStep = () => goToStep(WizardStepsScanner.SCAN);
70+
71+
const goToInfoStep = () => goToStep(WizardStepsScanner.OISY_SCANNER_INFO);
72+
73+
const startWalletConnect = async (uri: string) => {
74+
goToStep(WizardStepsScanner.WALLET_CONNECT_REVIEW);
75+
76+
const { result } = await connectListener({ uri, onSessionDeleteCallback: goToScanStep });
77+
78+
if (result === 'error') {
79+
goToStep(WizardStepsScanner.SCAN);
80+
}
81+
};
82+
83+
const onWalletConnectConnect = async (uri: string) => {
84+
await startWalletConnect(uri);
6885
};
6986
70-
const onNext = (results: ScannerResults) => {
87+
const onNext = async ({ results, code }: { results: ScannerResults; code?: string }) => {
7188
if (results === ScannerResults.PAY) {
7289
goToStep(WizardStepsScanner.PAY);
7390
7491
return;
7592
}
7693
7794
if (results === ScannerResults.WALLET_CONNECT) {
78-
// TODO: implement wallet connect flow
95+
if (isNullish(code)) {
96+
return;
97+
}
98+
99+
await startWalletConnect(code);
79100
80101
return;
81102
}
82103
83104
assertNever(results, `Unhandled scanner result: ${results}`);
84105
};
106+
107+
// When WalletConnectSession opens the scanner with a WC URI (deep-link), navigate to WC review once the modal renders
108+
let walletConnectDeepLinkHandled = $state(false);
109+
110+
$effect(() => {
111+
if (
112+
!walletConnectDeepLinkHandled &&
113+
nonNullish($modalUniversalScannerData?.walletConnectUri) &&
114+
nonNullish(modal)
115+
) {
116+
walletConnectDeepLinkHandled = true;
117+
118+
const uri = $modalUniversalScannerData.walletConnectUri;
119+
120+
untrack(() => startWalletConnect(uri));
121+
}
122+
});
85123
</script>
86124

87125
<ScannerModalPayDataLoader>
88-
<WizardModal
89-
bind:this={modal}
90-
disablePointerEvents={currentStep?.name === WizardStepsScanner.TOKENS_LIST}
91-
{onClose}
92-
{steps}
93-
bind:currentStep
94-
>
95-
{#snippet title()}
96-
{currentStep?.title}
97-
{/snippet}
98-
99-
{#key currentStep?.name}
100-
{#if currentStep?.name === WizardStepsScanner.SCAN}
101-
<ScannerCode {onNext} />
102-
{:else if currentStep?.name === WizardStepsScanner.PAY || currentStep?.name === WizardStepsScanner.TOKENS_LIST || currentStep?.name === WizardStepsScanner.PAYING || currentStep?.name === WizardStepsScanner.PAYMENT_FAILED || currentStep?.name === WizardStepsScanner.PAYMENT_CONFIRMED}
103-
<OpenCryptoPayWizard {currentStep} {modal} {steps} bind:payProgressStep />
104-
{:else if currentStep?.name === WizardStepsScanner.WALLET_CONNECT_CONNECT || currentStep?.name === WizardStepsScanner.WALLET_CONNECT_REVIEW}
105-
<WalletConnectSessionWizard {currentStep} onConnect={onWalletConnectConnect} />
106-
{/if}
107-
{/key}
108-
</WizardModal>
126+
<div class="scanner-modal">
127+
<WizardModal
128+
bind:this={modal}
129+
disablePointerEvents={currentStep?.name === WizardStepsScanner.TOKENS_LIST}
130+
{onClose}
131+
{steps}
132+
bind:currentStep
133+
>
134+
{#snippet title()}
135+
{currentStep?.title}
136+
{/snippet}
137+
138+
{#key currentStep?.name}
139+
{#if currentStep?.name === WizardStepsScanner.OISY_SCANNER_INFO}
140+
<ScannerInfo onButtonClick={goToScanStep} />
141+
{:else if currentStep?.name === WizardStepsScanner.SCAN}
142+
<ScannerCode {onNext} onOpenInfo={goToInfoStep} />
143+
{:else if currentStep?.name === WizardStepsScanner.PAY || currentStep?.name === WizardStepsScanner.TOKENS_LIST || currentStep?.name === WizardStepsScanner.PAYING || currentStep?.name === WizardStepsScanner.PAYMENT_FAILED || currentStep?.name === WizardStepsScanner.PAYMENT_CONFIRMED}
144+
<OpenCryptoPayWizard {currentStep} {modal} {steps} bind:payProgressStep />
145+
{:else if currentStep?.name === WizardStepsScanner.WALLET_CONNECT_CONNECT || currentStep?.name === WizardStepsScanner.WALLET_CONNECT_REVIEW}
146+
<WalletConnectSessionWizard {currentStep} onConnect={onWalletConnectConnect} />
147+
{/if}
148+
{/key}
149+
</WizardModal>
150+
</div>
109151
</ScannerModalPayDataLoader>
152+
153+
<style lang="scss">
154+
.scanner-modal :global(.content) {
155+
--dialog-padding-x: 0px;
156+
--dialog-padding-y: 0px;
157+
}
158+
</style>

src/frontend/src/lib/config/scanner.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ export const scannerWizardSteps = ({
99
name: WizardStepsScanner.SCAN,
1010
title: i18n.scanner.text.scan_qr_code
1111
},
12+
{
13+
name: WizardStepsScanner.OISY_SCANNER_INFO,
14+
title: i18n.scanner.text.scan_qr_code
15+
},
1216
{
1317
name: WizardStepsScanner.PAY,
1418
title: i18n.scanner.text.pay

src/frontend/src/lib/enums/wizard-steps.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export const WizardStepsGetToken = {
101101
} as const;
102102

103103
export enum WizardStepsScanner {
104+
OISY_SCANNER_INFO = 'Oisy Scanner Info',
104105
SCAN = 'Scan',
105106
PAY = 'Pay',
106107
TOKENS_LIST = 'Tokens List',

0 commit comments

Comments
 (0)