@block-auth.io/angular 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +37 -0
- package/.turbo/turbo-lint.log +4 -0
- package/.turbo/turbo-test.log +82 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +10 -0
- package/ng-package.json +6 -0
- package/package.json +50 -0
- package/scripts/css-to-ts.mjs +14 -0
- package/src/lib/__tests__/integration.test.ts +45 -0
- package/src/lib/block-auth.service.test.ts +69 -0
- package/src/lib/block-auth.service.ts +81 -0
- package/src/lib/components/btns/btn-base-provider.component.test.ts +22 -0
- package/src/lib/components/btns/btn-base-provider.component.ts +55 -0
- package/src/lib/components/btns/btn-block-auth.component.test.ts +79 -0
- package/src/lib/components/btns/btn-block-auth.component.ts +104 -0
- package/src/lib/components/btns/btn-gmail.component.test.ts +16 -0
- package/src/lib/components/btns/btn-gmail.component.ts +41 -0
- package/src/lib/components/btns/btn-google-base.component.test.ts +16 -0
- package/src/lib/components/btns/btn-google-base.component.ts +73 -0
- package/src/lib/components/btns/btn-metamask.component.test.ts +20 -0
- package/src/lib/components/btns/btn-metamask.component.ts +98 -0
- package/src/lib/components/btns/btn-microsoft.component.test.ts +16 -0
- package/src/lib/components/btns/btn-microsoft.component.ts +77 -0
- package/src/lib/components/btns/btn-phantom.component.test.ts +20 -0
- package/src/lib/components/btns/btn-phantom.component.ts +93 -0
- package/src/lib/components/btns/btn-signin.component.test.ts +55 -0
- package/src/lib/components/btns/btn-signin.component.ts +108 -0
- package/src/lib/components/btns/btn-wallet-connect.component.test.ts +20 -0
- package/src/lib/components/btns/btn-wallet-connect.component.ts +109 -0
- package/src/lib/components/btns/index.ts +11 -0
- package/src/lib/components/contents/content-signin.component.test.ts +33 -0
- package/src/lib/components/contents/content-signin.component.ts +68 -0
- package/src/lib/components/contents/content-signup.component.test.ts +78 -0
- package/src/lib/components/contents/content-signup.component.ts +225 -0
- package/src/lib/components/dds/change-account.component.test.ts +66 -0
- package/src/lib/components/dds/change-account.component.ts +112 -0
- package/src/lib/components/dds/dropdown-profile-btn.component.test.ts +66 -0
- package/src/lib/components/dds/dropdown-profile-btn.component.ts +185 -0
- package/src/lib/components/dds/dropdown-profile-item.component.test.ts +75 -0
- package/src/lib/components/dds/dropdown-profile-item.component.ts +74 -0
- package/src/lib/components/dds/dropdown-profile.component.test.ts +98 -0
- package/src/lib/components/dds/dropdown-profile.component.ts +258 -0
- package/src/lib/components/flow-block-auth.component.test.ts +93 -0
- package/src/lib/components/flow-block-auth.component.ts +226 -0
- package/src/lib/components/modals/modal-overlay.component.test.ts +81 -0
- package/src/lib/components/modals/modal-overlay.component.ts +43 -0
- package/src/lib/components/modals/modal-restoration-expired.component.test.ts +71 -0
- package/src/lib/components/modals/modal-restoration-expired.component.ts +83 -0
- package/src/lib/components/modals/modal-restore.component.test.ts +74 -0
- package/src/lib/components/modals/modal-restore.component.ts +104 -0
- package/src/lib/components/modals/modal-signin.component.test.ts +66 -0
- package/src/lib/components/modals/modal-signin.component.ts +44 -0
- package/src/lib/components/modals/modal-signup.component.test.ts +56 -0
- package/src/lib/components/modals/modal-signup.component.ts +40 -0
- package/src/lib/components/sections/footer-section.component.test.ts +19 -0
- package/src/lib/components/sections/footer-section.component.ts +49 -0
- package/src/lib/components/sections/internals/providers-buttons.component.test.ts +27 -0
- package/src/lib/components/sections/internals/providers-buttons.component.ts +290 -0
- package/src/lib/components/sections/internals/providers-filters.component.test.ts +25 -0
- package/src/lib/components/sections/internals/providers-filters.component.ts +139 -0
- package/src/lib/components/sections/internals/selected-button.component.test.ts +36 -0
- package/src/lib/components/sections/internals/selected-button.component.ts +92 -0
- package/src/lib/components/sections/login-section.component.test.ts +49 -0
- package/src/lib/components/sections/login-section.component.ts +120 -0
- package/src/lib/components/sections/providers-section.component.test.ts +22 -0
- package/src/lib/components/sections/providers-section.component.ts +187 -0
- package/src/lib/components/txns/internals/permissions-required-modal.component.test.ts +14 -0
- package/src/lib/components/txns/internals/permissions-required-modal.component.ts +35 -0
- package/src/lib/components/txns/internals/solicited-data-modal.component.test.ts +72 -0
- package/src/lib/components/txns/internals/solicited-data-modal.component.ts +187 -0
- package/src/lib/components/txns/internals/txn-error.component.test.ts +100 -0
- package/src/lib/components/txns/internals/txn-error.component.ts +121 -0
- package/src/lib/components/txns/internals/txn-type.component.test.ts +52 -0
- package/src/lib/components/txns/internals/txn-type.component.ts +51 -0
- package/src/lib/components/txns/internals/txn.component.test.ts +188 -0
- package/src/lib/components/txns/internals/txn.component.ts +298 -0
- package/src/lib/components/txns/txns-overview.component.test.ts +107 -0
- package/src/lib/components/txns/txns-overview.component.ts +229 -0
- package/src/lib/icons/ai-outline-disconnect.component.ts +21 -0
- package/src/lib/icons/bi-logo-gmail.component.ts +21 -0
- package/src/lib/icons/cg-profile.component.ts +31 -0
- package/src/lib/icons/check-icon.component.ts +27 -0
- package/src/lib/icons/ci-clock2.component.ts +31 -0
- package/src/lib/icons/fa-arrow-right.component.ts +21 -0
- package/src/lib/icons/fa-times.component.ts +21 -0
- package/src/lib/icons/fa-undo.component.ts +21 -0
- package/src/lib/icons/icons.test.ts +25 -0
- package/src/lib/icons/index.ts +22 -0
- package/src/lib/icons/io-ios-arrow-back.component.ts +21 -0
- package/src/lib/icons/io-ios-arrow-forward.component.ts +21 -0
- package/src/lib/icons/io-md-arrow-back.component.ts +19 -0
- package/src/lib/icons/io-md-arrow-forward.component.ts +19 -0
- package/src/lib/icons/pi-warning-fill.component.ts +21 -0
- package/src/lib/icons/tb-arrows-exchange2.component.ts +30 -0
- package/src/lib/icons/tb-copy-check-filled.component.ts +21 -0
- package/src/lib/icons/tb-copy.component.ts +34 -0
- package/src/lib/icons/ti-vendor-microsoft.component.ts +21 -0
- package/src/lib/inject-store.test.ts +26 -0
- package/src/lib/inject-store.ts +16 -0
- package/src/lib/primitives/arrow-icon.component.test.ts +12 -0
- package/src/lib/primitives/arrow-icon.component.ts +38 -0
- package/src/lib/primitives/btn-arrow-dir.component.test.ts +22 -0
- package/src/lib/primitives/btn-arrow-dir.component.ts +65 -0
- package/src/lib/primitives/btn-base.component.test.ts +38 -0
- package/src/lib/primitives/btn-base.component.ts +140 -0
- package/src/lib/primitives/divider-base.component.test.ts +12 -0
- package/src/lib/primitives/divider-base.component.ts +74 -0
- package/src/lib/primitives/dropdown-arrow.component.test.ts +12 -0
- package/src/lib/primitives/dropdown-arrow.component.ts +46 -0
- package/src/lib/primitives/dropdown-base.component.test.ts +49 -0
- package/src/lib/primitives/dropdown-base.component.ts +329 -0
- package/src/lib/primitives/helpers.ts +151 -0
- package/src/lib/primitives/horizontal-loader.component.test.ts +19 -0
- package/src/lib/primitives/horizontal-loader.component.ts +70 -0
- package/src/lib/primitives/icon-base.component.test.ts +19 -0
- package/src/lib/primitives/icon-base.component.ts +58 -0
- package/src/lib/primitives/img-loader.component.test.ts +12 -0
- package/src/lib/primitives/img-loader.component.ts +118 -0
- package/src/lib/primitives/index.ts +29 -0
- package/src/lib/primitives/input-base.component.test.ts +20 -0
- package/src/lib/primitives/input-base.component.ts +210 -0
- package/src/lib/primitives/lifetime-slider-base.component.test.ts +88 -0
- package/src/lib/primitives/lifetime-slider-base.component.ts +75 -0
- package/src/lib/primitives/oval-spinner.component.test.ts +9 -0
- package/src/lib/primitives/oval-spinner.component.ts +158 -0
- package/src/lib/primitives/pickers/dropdown-picker-animated-base.component.ts +220 -0
- package/src/lib/primitives/pickers/picker-button.component.ts +74 -0
- package/src/lib/primitives/pickers/time-picker-dropdown-selector.component.ts +188 -0
- package/src/lib/primitives/pickers/time-picker.component.ts +124 -0
- package/src/lib/primitives/pickers/time-unit-selector.component.ts +82 -0
- package/src/lib/primitives/progressbar.component.test.ts +18 -0
- package/src/lib/primitives/progressbar.component.ts +93 -0
- package/src/lib/primitives/providersStyles.ts +268 -0
- package/src/lib/primitives/slider-base.component.ts +56 -0
- package/src/lib/provide-block-auth.test.ts +43 -0
- package/src/lib/provide-block-auth.ts +43 -0
- package/src/lib/runtime/block-auth-runtime.directive.test.ts +89 -0
- package/src/lib/runtime/block-auth-runtime.directive.ts +68 -0
- package/src/lib/teleport-body.directive.test.ts +29 -0
- package/src/lib/teleport-body.directive.ts +21 -0
- package/src/lib/testing.ts +15 -0
- package/src/lib/transitions.css +23 -0
- package/src/public-api.ts +85 -0
- package/src/vitest.setup.ts +8 -0
- package/tailwind.config.cjs +6 -0
- package/tsconfig.json +15 -0
- package/tsconfig.lib.json +9 -0
- package/vite.config.mts +44 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
Component,
|
|
4
|
+
DestroyRef,
|
|
5
|
+
computed,
|
|
6
|
+
effect,
|
|
7
|
+
inject,
|
|
8
|
+
input,
|
|
9
|
+
output,
|
|
10
|
+
signal,
|
|
11
|
+
} from '@angular/core';
|
|
12
|
+
import { BtnBaseComponent } from '../../primitives/btn-base.component';
|
|
13
|
+
import { injectBlockAuthClient } from '../../provide-block-auth';
|
|
14
|
+
import { injectStore } from '../../inject-store';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Botón "Log In" genérico que dispara el flujo de firma vía
|
|
18
|
+
* `client.requestSignin()`.
|
|
19
|
+
*
|
|
20
|
+
* En click: guarda contra reentrada mientras hay una firma en curso
|
|
21
|
+
* (`isConnecting || isSigning`), marca `isSigning` + el loader del botón, llama
|
|
22
|
+
* a `requestSignin()` (emite `user:txns:create`) y abre el modal de signin con
|
|
23
|
+
* `isSigningLoader = true` para que se abra cubierto por el spinner mientras
|
|
24
|
+
* llegan las txns pendientes (`ContentSignin` lo limpia al recibirlas).
|
|
25
|
+
*
|
|
26
|
+
* Escucha en `document` los eventos `ext:txn:response` / `ext:txn:closed`
|
|
27
|
+
* (listeners registrados en el constructor, limpiados con `DestroyRef`), y un
|
|
28
|
+
* `effect()` sobre `ui().isSigning`.
|
|
29
|
+
*/
|
|
30
|
+
@Component({
|
|
31
|
+
selector: 'ba-btn-signin',
|
|
32
|
+
standalone: true,
|
|
33
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
34
|
+
imports: [BtnBaseComponent],
|
|
35
|
+
template: `
|
|
36
|
+
<ba-btn-base
|
|
37
|
+
btnId="btn-login-signup"
|
|
38
|
+
type="button-outlined"
|
|
39
|
+
[text]="text()"
|
|
40
|
+
size="small"
|
|
41
|
+
[isIcon]="false"
|
|
42
|
+
paddingClassNames="bas-px-8"
|
|
43
|
+
[disabled]="isDisabled() || isConnecting() || ui().isSigning"
|
|
44
|
+
[applyDisabledStyles]="false"
|
|
45
|
+
[isFocusOutlinedEnabled]="false"
|
|
46
|
+
textClassNames="bas-text-white bas-font-semibold bas-leading-6 bas-text-base"
|
|
47
|
+
[bgClassNames]="bgClassNames()"
|
|
48
|
+
[isLoader]="isConnecting() || ui().isSigning"
|
|
49
|
+
(action)="onBtnSigninClicked()"
|
|
50
|
+
/>
|
|
51
|
+
`,
|
|
52
|
+
})
|
|
53
|
+
export class BtnSigninComponent {
|
|
54
|
+
text = input('Log In');
|
|
55
|
+
isDisabled = input(false);
|
|
56
|
+
isEditable = input(false);
|
|
57
|
+
|
|
58
|
+
clicked = output<void>();
|
|
59
|
+
|
|
60
|
+
private client = injectBlockAuthClient();
|
|
61
|
+
ui = injectStore(this.client.state.$ui);
|
|
62
|
+
isConnecting = signal(false);
|
|
63
|
+
|
|
64
|
+
bgClassNames = computed(() =>
|
|
65
|
+
this.isDisabled()
|
|
66
|
+
? 'bas-bg-[var(--bal-primary-500)] hover:bas-bg-[var(--bal-primary-700)]'
|
|
67
|
+
: 'bas-bg-[rgba(0,78,152,0.8)] hover:bas-bg-[rgba(0,62,121,0.8)]',
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
constructor() {
|
|
71
|
+
const destroyRef = inject(DestroyRef);
|
|
72
|
+
const handleTxnResponse = () => this.isConnecting.set(false);
|
|
73
|
+
const handleTxnClosed = () => {
|
|
74
|
+
this.client.state.$ui.setKey('isSigning', false);
|
|
75
|
+
this.isConnecting.set(false);
|
|
76
|
+
};
|
|
77
|
+
document.addEventListener('ext:txn:response', handleTxnResponse);
|
|
78
|
+
document.addEventListener('ext:txn:closed', handleTxnClosed);
|
|
79
|
+
destroyRef.onDestroy(() => {
|
|
80
|
+
document.removeEventListener('ext:txn:response', handleTxnResponse);
|
|
81
|
+
document.removeEventListener('ext:txn:closed', handleTxnClosed);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
effect(() => {
|
|
85
|
+
if (!this.ui().isSigning) this.isConnecting.set(false);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
onBtnSigninClicked(): void {
|
|
90
|
+
if (this.isEditable()) return;
|
|
91
|
+
if (this.isConnecting() || this.ui().isSigning) return;
|
|
92
|
+
|
|
93
|
+
this.isConnecting.set(true);
|
|
94
|
+
this.client.state.$ui.setKey('isSigning', true);
|
|
95
|
+
|
|
96
|
+
this.client.requestSignin();
|
|
97
|
+
|
|
98
|
+
const uiState = this.client.state.$ui.get();
|
|
99
|
+
const serviceName = uiState.serviceName ?? 'blockauth';
|
|
100
|
+
const enabled = (uiState.uiConfig as { config?: Record<string, { enabled?: boolean }> } | null)
|
|
101
|
+
?.config?.[serviceName]?.enabled;
|
|
102
|
+
if (enabled) {
|
|
103
|
+
this.client.openModal('signin');
|
|
104
|
+
this.client.state.$ui.setKey('isSigningLoader', true);
|
|
105
|
+
}
|
|
106
|
+
this.clicked.emit();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/angular';
|
|
3
|
+
import { map } from 'nanostores';
|
|
4
|
+
import { BtnWalletConnectComponent } from './btn-wallet-connect.component';
|
|
5
|
+
import { provideBlockAuthClientForTest } from '../../testing';
|
|
6
|
+
|
|
7
|
+
describe('BtnWalletConnect', () => {
|
|
8
|
+
it('al hacer click delega en client.wallet.evm.connectWalletConnect()', async () => {
|
|
9
|
+
const connectSpy = vi.fn(async () => undefined);
|
|
10
|
+
const fakeClient = {
|
|
11
|
+
wallet: { evm: { connectWalletConnect: connectSpy } },
|
|
12
|
+
state: { $wallet: map({ address: null, isConnecting: false }) },
|
|
13
|
+
} as any;
|
|
14
|
+
await render(BtnWalletConnectComponent, {
|
|
15
|
+
providers: [provideBlockAuthClientForTest(fakeClient)],
|
|
16
|
+
});
|
|
17
|
+
await fireEvent.click(screen.getByRole('button'));
|
|
18
|
+
await waitFor(() => expect(connectSpy).toHaveBeenCalled());
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core';
|
|
2
|
+
import { BtnBaseComponent } from '../../primitives/btn-base.component';
|
|
3
|
+
import { providersStyles } from '../../primitives/providersStyles';
|
|
4
|
+
import { injectBlockAuthClient } from '../../provide-block-auth';
|
|
5
|
+
import { injectStore } from '../../inject-store';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Botón WalletConnect. El click delega en
|
|
9
|
+
* `client.wallet.evm.connectWalletConnect()`. Los colores/iconos de marca por
|
|
10
|
+
* wallet salen del primitivo `providersStyles` indexado por `walletService`.
|
|
11
|
+
*/
|
|
12
|
+
const bgByWallet: Record<string, string> = {
|
|
13
|
+
trustwallet: 'bas-bg-[#140dda] hover:bas-bg-[#140dda]',
|
|
14
|
+
binance: 'bas-bg-[#464646] hover:bas-bg-[#191919]',
|
|
15
|
+
uniswap: 'bas-bg-[#f64399] hover:bas-bg-[#d76fa1]',
|
|
16
|
+
};
|
|
17
|
+
const styles = {
|
|
18
|
+
disabledBgClassNames: 'bas-bg-[rgba(235,89,0,0.75)] hover:bas-bg-[rgba(216,81,0,0.75)]',
|
|
19
|
+
ringClassNames: 'bal-ba-hover-ring-gray-400',
|
|
20
|
+
paddingClassNames: 'bas-px-12',
|
|
21
|
+
textClassNames: 'bas-text-white bas-font-semibold bas-leading-6 bas-text-base',
|
|
22
|
+
loaderWidth: 70,
|
|
23
|
+
loaderColor: '#fefefe',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
@Component({
|
|
27
|
+
selector: 'ba-btn-wallet-connect',
|
|
28
|
+
standalone: true,
|
|
29
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
30
|
+
imports: [BtnBaseComponent],
|
|
31
|
+
template: `
|
|
32
|
+
@if (isDropdownProfile()) {
|
|
33
|
+
<button
|
|
34
|
+
[id]="'btn-provider-' + walletServiceFormatted() + '-dropdown'"
|
|
35
|
+
class="bas-h-full bas-w-full bas-border-0 bas-bg-transparent bas-p-0"
|
|
36
|
+
(click)="onWalletSigning()"
|
|
37
|
+
><ng-content /></button>
|
|
38
|
+
} @else {
|
|
39
|
+
<ba-btn-base
|
|
40
|
+
[btnId]="'btn-provider-' + walletServiceFormatted()"
|
|
41
|
+
[text]="btnText()"
|
|
42
|
+
type="button-outlined"
|
|
43
|
+
size="small"
|
|
44
|
+
[bgClassNames]="wallet().isConnecting ? disabledBg : bgClassNames()"
|
|
45
|
+
[ringClassNames]="ring"
|
|
46
|
+
[paddingClassNames]="padding"
|
|
47
|
+
[textClassNames]="textCls"
|
|
48
|
+
[isLoader]="wallet().isConnecting"
|
|
49
|
+
[loaderWidth]="loaderWidth"
|
|
50
|
+
[loaderColor]="loaderColor"
|
|
51
|
+
[isIcon]="iconEnabled()"
|
|
52
|
+
[isFocusOutlinedEnabled]="false"
|
|
53
|
+
[disabled]="disabled()"
|
|
54
|
+
(action)="onWalletSigning()"
|
|
55
|
+
>
|
|
56
|
+
<img
|
|
57
|
+
slot="icon"
|
|
58
|
+
[src]="style()?.imgSrc ?? ''"
|
|
59
|
+
height="24"
|
|
60
|
+
width="24"
|
|
61
|
+
class="bas-rounded-lg"
|
|
62
|
+
[alt]="style()?.name ?? walletService()"
|
|
63
|
+
/>
|
|
64
|
+
</ba-btn-base>
|
|
65
|
+
}
|
|
66
|
+
`,
|
|
67
|
+
})
|
|
68
|
+
export class BtnWalletConnectComponent {
|
|
69
|
+
isDropdownProfile = input(false);
|
|
70
|
+
text = input('Connect Wallet');
|
|
71
|
+
isTextInsteadOfAccount = input(false);
|
|
72
|
+
iconEnabled = input(true);
|
|
73
|
+
disabled = input(false);
|
|
74
|
+
isEditable = input(false);
|
|
75
|
+
walletService = input('Trust Wallet');
|
|
76
|
+
|
|
77
|
+
clicked = output<void>();
|
|
78
|
+
|
|
79
|
+
private client = injectBlockAuthClient();
|
|
80
|
+
wallet = injectStore(this.client.state.$wallet);
|
|
81
|
+
|
|
82
|
+
ring = styles.ringClassNames;
|
|
83
|
+
padding = styles.paddingClassNames;
|
|
84
|
+
textCls = styles.textClassNames;
|
|
85
|
+
disabledBg = styles.disabledBgClassNames;
|
|
86
|
+
loaderWidth = styles.loaderWidth;
|
|
87
|
+
loaderColor = styles.loaderColor;
|
|
88
|
+
|
|
89
|
+
walletServiceFormatted = computed(() => this.walletService().toLowerCase().replace(' ', ''));
|
|
90
|
+
style = computed(() => providersStyles[this.walletServiceFormatted()]);
|
|
91
|
+
bgClassNames = computed(
|
|
92
|
+
() => bgByWallet[this.walletServiceFormatted()] ?? 'bas-bg-[#140dda] hover:bas-bg-[#140dda]',
|
|
93
|
+
);
|
|
94
|
+
btnText = computed(() => {
|
|
95
|
+
const short = (a: string | null) => (a ? `${a.slice(0, 6)}…${a.slice(-4)}` : '');
|
|
96
|
+
return this.isTextInsteadOfAccount() ? this.text() : short(this.wallet().address) || this.text();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
async onWalletSigning(): Promise<void> {
|
|
100
|
+
if (this.isEditable()) return;
|
|
101
|
+
if (this.wallet().isConnecting) return;
|
|
102
|
+
try {
|
|
103
|
+
await this.client.wallet.evm.connectWalletConnect();
|
|
104
|
+
} catch {
|
|
105
|
+
/* connection cancelled/failed */
|
|
106
|
+
}
|
|
107
|
+
this.clicked.emit();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Barrel de los botones de proveedor de @block-auth.io/angular (Task A7),
|
|
2
|
+
// espejo 1:1 de packages/react/src/components/btns/*.
|
|
3
|
+
export { BtnBaseProviderComponent } from './btn-base-provider.component';
|
|
4
|
+
export { BtnBlockAuthComponent } from './btn-block-auth.component';
|
|
5
|
+
export { BtnGmailComponent } from './btn-gmail.component';
|
|
6
|
+
export { BtnGoogleBaseComponent } from './btn-google-base.component';
|
|
7
|
+
export { BtnMetamaskComponent } from './btn-metamask.component';
|
|
8
|
+
export { BtnMicrosoftComponent } from './btn-microsoft.component';
|
|
9
|
+
export { BtnPhantomComponent } from './btn-phantom.component';
|
|
10
|
+
export { BtnSigninComponent } from './btn-signin.component';
|
|
11
|
+
export { BtnWalletConnectComponent } from './btn-wallet-connect.component';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/angular';
|
|
3
|
+
import { map } from 'nanostores';
|
|
4
|
+
import { ContentSigninComponent } from './content-signin.component';
|
|
5
|
+
import { provideBlockAuthClientForTest } from '../../testing';
|
|
6
|
+
|
|
7
|
+
vi.mock('@wagmi/core', () => ({
|
|
8
|
+
createConfig: vi.fn(() => ({ connectors: [] })),
|
|
9
|
+
connect: vi.fn(), disconnect: vi.fn(), getAccount: vi.fn(() => ({})),
|
|
10
|
+
signMessage: vi.fn(), watchAccount: vi.fn(() => () => undefined), http: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
vi.mock('@wagmi/connectors', () => ({
|
|
13
|
+
injected: vi.fn(() => ({ id: 'injected' })), walletConnect: vi.fn(() => ({ id: 'walletConnect' })),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
function makeClient() {
|
|
17
|
+
return { state: { $ws: map({}), $ui: map({}) } } as any;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Port de packages/react/src/components/contents/ContentSignin.test.tsx (con
|
|
21
|
+
// el cross-check del port Vue que verifica el overview real: A10 cableó el
|
|
22
|
+
// placeholder de A9 al `<ba-txns-overview>` real).
|
|
23
|
+
describe('ContentSignin', () => {
|
|
24
|
+
it('renderiza el contenido de signin', async () => {
|
|
25
|
+
await render(ContentSigninComponent, { providers: [provideBlockAuthClientForTest(makeClient())] });
|
|
26
|
+
expect(screen.getByTestId('content-signin')).toBeDefined();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('sin spinner (estado por defecto) renderiza el overview de txns real', async () => {
|
|
30
|
+
await render(ContentSigninComponent, { providers: [provideBlockAuthClientForTest(makeClient())] });
|
|
31
|
+
expect(screen.getByTestId('txns-overview')).toBeDefined();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, effect, input, signal } from '@angular/core';
|
|
2
|
+
import { injectBlockAuthClient } from '../../provide-block-auth';
|
|
3
|
+
import { injectStore } from '../../inject-store';
|
|
4
|
+
import { OvalSpinnerComponent } from '../../primitives/oval-spinner.component';
|
|
5
|
+
import { TxnsOverviewComponent } from '../txns/txns-overview.component';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Contenido de sign-in dirigido por el core (overview de transacciones).
|
|
9
|
+
* Muestra un `<ba-oval-spinner>` mientras `isAcceptingTxn || isSigningLoader`,
|
|
10
|
+
* luego el overview de transacciones. En el montaje emite `user:txns { token }`
|
|
11
|
+
* y auto-limpia el signing loader al recibir transacciones (debounce 200ms) o
|
|
12
|
+
* tras 1.5s.
|
|
13
|
+
*
|
|
14
|
+
* El `effect()` reprograma los timers cada vez que cambian `ws.requests` (o
|
|
15
|
+
* `isLoaded`), y emite `user:txns` una única vez guardado en `isLoaded`.
|
|
16
|
+
* Escribir `isLoaded.set(true)` dentro del propio effect que lee `isLoaded`
|
|
17
|
+
* dispara una segunda ejecución inmediata (limpieza+reprogramación de timers).
|
|
18
|
+
*/
|
|
19
|
+
@Component({
|
|
20
|
+
selector: 'ba-content-signin',
|
|
21
|
+
standalone: true,
|
|
22
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
23
|
+
imports: [OvalSpinnerComponent, TxnsOverviewComponent],
|
|
24
|
+
template: `
|
|
25
|
+
<div data-testid="content-signin" class="bas-flex bas-h-full bas-w-full">
|
|
26
|
+
@if (showSpinner()) {
|
|
27
|
+
<ba-oval-spinner [showLogo]="true" [animateBg]="false" />
|
|
28
|
+
} @else {
|
|
29
|
+
<ba-txns-overview [closeTxns]="closeTxns()" />
|
|
30
|
+
}
|
|
31
|
+
</div>
|
|
32
|
+
`,
|
|
33
|
+
})
|
|
34
|
+
export class ContentSigninComponent {
|
|
35
|
+
closeTxns = input<() => void>(() => {});
|
|
36
|
+
|
|
37
|
+
private readonly client = injectBlockAuthClient();
|
|
38
|
+
private readonly ws = injectStore(this.client.state.$ws);
|
|
39
|
+
private readonly ui = injectStore(this.client.state.$ui);
|
|
40
|
+
private readonly isLoaded = signal(false);
|
|
41
|
+
|
|
42
|
+
protected showSpinner = computed(() => {
|
|
43
|
+
const ui = this.ui() as Record<string, any>;
|
|
44
|
+
return !!ui.isAcceptingTxn || !!ui.isSigningLoader;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
constructor() {
|
|
48
|
+
effect((onCleanup) => {
|
|
49
|
+
const requests = (this.ws() as Record<string, any>).requests ?? [];
|
|
50
|
+
const loaded = this.isLoaded();
|
|
51
|
+
const t1 = setTimeout(() => {
|
|
52
|
+
if (requests.length > 0) this.client.state.$ui.setKey('isSigningLoader', false);
|
|
53
|
+
}, 200);
|
|
54
|
+
const t2 = setTimeout(() => {
|
|
55
|
+
this.client.state.$ui.setKey('isSigningLoader', false);
|
|
56
|
+
}, 1500);
|
|
57
|
+
if (!loaded && this.client.provider?.wsc) {
|
|
58
|
+
const s = this.client.state.$ws.get();
|
|
59
|
+
this.client.provider.wsc.emit('user:txns', { token: (s as Record<string, any>).token });
|
|
60
|
+
this.isLoaded.set(true);
|
|
61
|
+
}
|
|
62
|
+
onCleanup(() => {
|
|
63
|
+
clearTimeout(t1);
|
|
64
|
+
clearTimeout(t2);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/angular';
|
|
3
|
+
import { map } from 'nanostores';
|
|
4
|
+
|
|
5
|
+
vi.mock('@wagmi/core', () => ({
|
|
6
|
+
createConfig: vi.fn(() => ({ connectors: [] })),
|
|
7
|
+
connect: vi.fn(), disconnect: vi.fn(), getAccount: vi.fn(() => ({})),
|
|
8
|
+
signMessage: vi.fn(), watchAccount: vi.fn(() => () => undefined), http: vi.fn(),
|
|
9
|
+
}));
|
|
10
|
+
vi.mock('@wagmi/connectors', () => ({
|
|
11
|
+
injected: vi.fn(() => ({ id: 'injected' })), walletConnect: vi.fn(() => ({ id: 'walletConnect' })),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
// Espía makeQrDataUrl para evitar canvas/qrcode en jsdom y verificar el
|
|
15
|
+
// cableado selección de servicio -> QR. deepLinkFor se deja real (función pura).
|
|
16
|
+
const FAKE_QR_DATA_URL = 'data:image/png;base64,FAKEQR==';
|
|
17
|
+
vi.mock('@block-auth.io/core', async (importOriginal) => {
|
|
18
|
+
const actual = await importOriginal<typeof import('@block-auth.io/core')>();
|
|
19
|
+
return { ...actual, makeQrDataUrl: vi.fn(async () => FAKE_QR_DATA_URL) };
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
import { ContentSignupComponent } from './content-signup.component';
|
|
23
|
+
import { makeQrDataUrl } from '@block-auth.io/core';
|
|
24
|
+
import { provideBlockAuthClientForTest } from '../../testing';
|
|
25
|
+
|
|
26
|
+
// El $ui es propio del client falso (no el singleton del core), así que el
|
|
27
|
+
// uiConfig se fija directamente en el map, sin setUiConfig ni reset global.
|
|
28
|
+
function makeClient(ui: Record<string, unknown> = {}) {
|
|
29
|
+
return {
|
|
30
|
+
state: {
|
|
31
|
+
$ui: map<Record<string, unknown>>({ ...ui }),
|
|
32
|
+
$ws: map({}),
|
|
33
|
+
$wallet: map({}),
|
|
34
|
+
$openModals: map({}),
|
|
35
|
+
},
|
|
36
|
+
} as any;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
vi.clearAllMocks();
|
|
41
|
+
(makeQrDataUrl as ReturnType<typeof vi.fn>).mockResolvedValue(FAKE_QR_DATA_URL);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Port de packages/react/src/components/contents/ContentSignup.test.tsx.
|
|
45
|
+
describe('ContentSignup', () => {
|
|
46
|
+
it('renderiza el contenido de signup', async () => {
|
|
47
|
+
await render(ContentSignupComponent, { providers: [provideBlockAuthClientForTest(makeClient())] });
|
|
48
|
+
expect(screen.getByTestId('content-signup')).toBeDefined();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('no llama a makeQrDataUrl cuando el servicio inicial no es web3', async () => {
|
|
52
|
+
// El servicio por defecto es 'blockauth' (FALLBACK_PROVIDERS[0]), no web3.
|
|
53
|
+
const client = makeClient({ uiConfig: { domain: 'app.com' } });
|
|
54
|
+
await render(ContentSignupComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
55
|
+
expect(makeQrDataUrl).not.toHaveBeenCalled();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('solo muestra los proveedores Web2 (google/microsoft/blockauth), no web3', async () => {
|
|
59
|
+
const client = makeClient({
|
|
60
|
+
uiConfig: {
|
|
61
|
+
domain: 'app.com',
|
|
62
|
+
config: {
|
|
63
|
+
blockauth: { enabled: true, orderIndex: 0 },
|
|
64
|
+
google: { enabled: true, orderIndex: 1 },
|
|
65
|
+
microsoft: { enabled: true, orderIndex: 2 },
|
|
66
|
+
metamask: { enabled: true, orderIndex: 3 },
|
|
67
|
+
phantom: { enabled: true, orderIndex: 4 },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
await render(ContentSignupComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
72
|
+
expect(screen.getByAltText('BlockAuth')).toBeDefined();
|
|
73
|
+
expect(screen.getByAltText('Google')).toBeDefined();
|
|
74
|
+
expect(screen.getByAltText('Microsoft')).toBeDefined();
|
|
75
|
+
expect(screen.queryByAltText('MetaMask')).toBeNull();
|
|
76
|
+
expect(screen.queryByAltText('Phantom')).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChangeDetectionStrategy,
|
|
3
|
+
Component,
|
|
4
|
+
computed,
|
|
5
|
+
effect,
|
|
6
|
+
input,
|
|
7
|
+
signal,
|
|
8
|
+
untracked,
|
|
9
|
+
} from '@angular/core';
|
|
10
|
+
import {
|
|
11
|
+
$locale,
|
|
12
|
+
deepLinkFor,
|
|
13
|
+
makeQrDataUrl,
|
|
14
|
+
t as translate,
|
|
15
|
+
type TextKey,
|
|
16
|
+
} from '@block-auth.io/core';
|
|
17
|
+
import { injectBlockAuthClient } from '../../provide-block-auth';
|
|
18
|
+
import { injectStore } from '../../inject-store';
|
|
19
|
+
import { providersStyles } from '../../primitives/providersStyles';
|
|
20
|
+
import { ProgressbarComponent } from '../../primitives/progressbar.component';
|
|
21
|
+
import { ProvidersSectionComponent } from '../sections/providers-section.component';
|
|
22
|
+
import { LoginSectionComponent } from '../sections/login-section.component';
|
|
23
|
+
import { FooterSectionComponent } from '../sections/footer-section.component';
|
|
24
|
+
import type { WebTypeItem } from '../sections/internals/providers-filters.component';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Contenido de sign-up: el `<ba-progressbar>` de 3 pasos, la
|
|
28
|
+
* `<ba-providers-section>` (cabecera + filtros + grid), la
|
|
29
|
+
* `<ba-login-section>` (logo + CTA + descripción) y la `<ba-footer-section>`,
|
|
30
|
+
* en el grid responsive de dos columnas.
|
|
31
|
+
*/
|
|
32
|
+
const ENABLED_PROVIDERS = ['google', 'microsoft', 'blockauth'];
|
|
33
|
+
const WEB2 = ['google', 'microsoft'];
|
|
34
|
+
const FALLBACK_PROVIDERS = ['blockauth', 'google', 'microsoft'];
|
|
35
|
+
|
|
36
|
+
function applyFilters(providers: string[], { webType, search }: { webType: string; search: string }): string[] {
|
|
37
|
+
let result = providers;
|
|
38
|
+
if (webType === 'web2') result = result.filter((item) => WEB2.includes(item));
|
|
39
|
+
else if (webType === 'web3') result = result.filter((item) => !WEB2.includes(item));
|
|
40
|
+
if (search) {
|
|
41
|
+
const q = search.toLowerCase();
|
|
42
|
+
result = result.filter((item) => item.toLowerCase().includes(q));
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Component({
|
|
48
|
+
selector: 'ba-content-signup',
|
|
49
|
+
standalone: true,
|
|
50
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
51
|
+
imports: [ProgressbarComponent, ProvidersSectionComponent, LoginSectionComponent, FooterSectionComponent],
|
|
52
|
+
template: `
|
|
53
|
+
<div
|
|
54
|
+
data-testid="content-signup"
|
|
55
|
+
[attr.data-device]="device()"
|
|
56
|
+
[class]="
|
|
57
|
+
'bas-grid bas-h-full ' +
|
|
58
|
+
(!isEditable()
|
|
59
|
+
? 'bas-w-full bas-grid-cols-1 bas-grid-rows-[52px_140px_228px_48px] sm1:bas-w-fit sm1:bas-grid-cols-[240px_384px] sm1:bas-grid-rows-[52px_auto_auto] lg:bas-grid-cols-[240px_640px]'
|
|
60
|
+
: 'bas-w-fit')
|
|
61
|
+
"
|
|
62
|
+
>
|
|
63
|
+
<ba-providers-section
|
|
64
|
+
[container]="container()"
|
|
65
|
+
[uiConfigProvidersInfo]="uiConfigProvidersInfo()"
|
|
66
|
+
[selectedService]="selectedService()"
|
|
67
|
+
(selectedServiceChange)="selectedService.set($event)"
|
|
68
|
+
[showedProviders]="showedProviders()"
|
|
69
|
+
(changeWebType)="onChangeWebType($event)"
|
|
70
|
+
(changeProviderSearch)="onChangeProviderSearch($event)"
|
|
71
|
+
[isEditable]="isEditable()"
|
|
72
|
+
[isEditing]="isEditing()"
|
|
73
|
+
[device]="device()"
|
|
74
|
+
/>
|
|
75
|
+
|
|
76
|
+
<section
|
|
77
|
+
[class]="
|
|
78
|
+
'bas-flex bas-items-center bas-justify-center ' + (!isEditable() ? 'bas-row-start-1 sm1:bas-row-start-auto' : '')
|
|
79
|
+
"
|
|
80
|
+
>
|
|
81
|
+
<ba-progressbar
|
|
82
|
+
[step1]="false"
|
|
83
|
+
[fillStep1]="true"
|
|
84
|
+
[step2]="!hasBeenConnected() && showImgLoader()"
|
|
85
|
+
[step3]="!showImgLoader()"
|
|
86
|
+
[goBackToStep2]="(serviceName() !== selectedService() || !isServiceConnected()) && hasBeenConnected()"
|
|
87
|
+
/>
|
|
88
|
+
</section>
|
|
89
|
+
|
|
90
|
+
<ba-login-section
|
|
91
|
+
[imgQR]="imgQR() ?? ''"
|
|
92
|
+
[showImgLoader]="showImgLoader()"
|
|
93
|
+
[selectedService]="selectedService()"
|
|
94
|
+
[isServiceConnected]="isServiceConnected()"
|
|
95
|
+
[isEditable]="isEditable()"
|
|
96
|
+
[device]="device()"
|
|
97
|
+
[btnsConnectTxt]="btnsConnectTxt()"
|
|
98
|
+
[loginTxt]="loginTxt()"
|
|
99
|
+
[lastStepTxt]="lastStepTxt()"
|
|
100
|
+
/>
|
|
101
|
+
|
|
102
|
+
<ba-footer-section
|
|
103
|
+
[isEditable]="isEditable()"
|
|
104
|
+
[device]="device() === 'phone' ? 'phone' : 'desktop'"
|
|
105
|
+
[text1]="footerText1()"
|
|
106
|
+
[text2]="footerText2()"
|
|
107
|
+
[href]="footerHref()"
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
`,
|
|
111
|
+
})
|
|
112
|
+
export class ContentSignupComponent {
|
|
113
|
+
container = input<HTMLElement | null>(null);
|
|
114
|
+
isEditable = input(false);
|
|
115
|
+
isEditing = input(false);
|
|
116
|
+
device = input<'desktop' | 'tablet' | 'phone'>('desktop');
|
|
117
|
+
|
|
118
|
+
private readonly client = injectBlockAuthClient();
|
|
119
|
+
private readonly ui = injectStore(this.client.state.$ui);
|
|
120
|
+
private readonly locale = injectStore($locale);
|
|
121
|
+
private readonly t = (key: TextKey): string => translate(key, this.locale());
|
|
122
|
+
|
|
123
|
+
// Etiquetas localizadas del text-map del backend, con fallback al i18n del core.
|
|
124
|
+
private readonly content = computed(() => (this.ui() as Record<string, any>)?.uiConfig?.config?.content);
|
|
125
|
+
protected btnsConnectTxt = computed(() => this.content()?.btns?.connect?.[this.locale()] ?? '');
|
|
126
|
+
protected loginTxt = computed(() => this.content()?.login?.[this.locale()] ?? this.t('login'));
|
|
127
|
+
protected lastStepTxt = computed(() => this.content()?.lastStep?.[this.locale()] ?? this.t('lastStep'));
|
|
128
|
+
protected footerText1 = computed(() => this.content()?.footer?.first?.[this.locale()] ?? this.t('footerFirst'));
|
|
129
|
+
protected footerText2 = computed(() => this.content()?.footer?.second?.[this.locale()] ?? this.t('footerSecond'));
|
|
130
|
+
protected footerHref = computed(() => this.content()?.footer?.url ?? 'https://block-auth.io');
|
|
131
|
+
|
|
132
|
+
protected serviceName = computed(() => (this.ui() as Record<string, any>)?.serviceName);
|
|
133
|
+
protected isServiceConnected = computed(() => !!(this.ui() as Record<string, any>)?.isServiceConnected);
|
|
134
|
+
private readonly domain = computed(
|
|
135
|
+
() => (this.ui() as Record<string, any>)?.uiConfig?.domain as string | undefined,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
protected uiConfigProvidersInfo = computed(() => {
|
|
139
|
+
const config = (this.ui() as Record<string, any>)?.uiConfig?.config;
|
|
140
|
+
if (!config) return undefined;
|
|
141
|
+
return {
|
|
142
|
+
providersColumns: Number(config.providersColumns) || undefined,
|
|
143
|
+
showProviderNameOnMobile: config.showProviderNameOnMobile,
|
|
144
|
+
enableSearch: config.enableSearch,
|
|
145
|
+
enableWebTypeFilter: config.enableWebTypeFilter,
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
private readonly allProviders = computed<string[]>(() => {
|
|
150
|
+
const config = (this.ui() as Record<string, any>)?.uiConfig?.config;
|
|
151
|
+
if (config) {
|
|
152
|
+
const enabled = Object.keys(config)
|
|
153
|
+
.filter(
|
|
154
|
+
(key) =>
|
|
155
|
+
ENABLED_PROVIDERS.includes(key) &&
|
|
156
|
+
providersStyles[key] &&
|
|
157
|
+
typeof config[key] === 'object' &&
|
|
158
|
+
config[key] !== null &&
|
|
159
|
+
'enabled' in config[key] &&
|
|
160
|
+
'orderIndex' in config[key],
|
|
161
|
+
)
|
|
162
|
+
.filter((key) => config[key].enabled)
|
|
163
|
+
.sort((a, b) => config[a].orderIndex - config[b].orderIndex);
|
|
164
|
+
if (enabled.length > 0) return enabled;
|
|
165
|
+
}
|
|
166
|
+
return FALLBACK_PROVIDERS;
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
protected selectedService = signal<string>(this.allProviders()[0] ?? 'blockauth');
|
|
170
|
+
protected showedProviders = signal<string[]>(this.allProviders());
|
|
171
|
+
protected hasBeenConnected = signal(false);
|
|
172
|
+
protected imgQR = signal<string | null>(null);
|
|
173
|
+
// Estado de filtros no reactivo: mutarlo no dispara reactividad.
|
|
174
|
+
private filterState: { webType: string; search: string } = { webType: 'all', search: '' };
|
|
175
|
+
|
|
176
|
+
protected showImgLoader = computed(
|
|
177
|
+
() => !(this.isServiceConnected() && this.serviceName() === this.selectedService()),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
constructor() {
|
|
181
|
+
// Efecto 1: re-deriva showedProviders/selectedService cuando cambia allProviders.
|
|
182
|
+
effect(() => {
|
|
183
|
+
const providers = this.allProviders();
|
|
184
|
+
this.showedProviders.set(applyFilters(providers, this.filterState));
|
|
185
|
+
const cur = untracked(() => this.selectedService());
|
|
186
|
+
this.selectedService.set(providers.includes(cur) ? cur : providers[0] ?? 'blockauth');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Efecto 2: marca hasBeenConnected cuando el servicio seleccionado conecta.
|
|
190
|
+
effect(() => {
|
|
191
|
+
if (this.serviceName() === this.selectedService() && this.isServiceConnected()) {
|
|
192
|
+
this.hasBeenConnected.set(true);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Efecto 3: genera el QR para providers web3 (metamask/phantom), con guard
|
|
197
|
+
// de cancelación.
|
|
198
|
+
effect((onCleanup) => {
|
|
199
|
+
const svc = this.selectedService();
|
|
200
|
+
const dom = this.domain();
|
|
201
|
+
const isWeb3 = svc === 'metamask' || svc === 'phantom';
|
|
202
|
+
if (!isWeb3 || !dom) {
|
|
203
|
+
this.imgQR.set(null);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
let cancelled = false;
|
|
207
|
+
const deepLink = deepLinkFor(svc, dom);
|
|
208
|
+
makeQrDataUrl(deepLink).then((dataUrl) => {
|
|
209
|
+
if (!cancelled) this.imgQR.set(dataUrl);
|
|
210
|
+
});
|
|
211
|
+
onCleanup(() => {
|
|
212
|
+
cancelled = true;
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
onChangeWebType(webType: WebTypeItem): void {
|
|
218
|
+
this.filterState = { ...this.filterState, webType: webType.value };
|
|
219
|
+
this.showedProviders.set(applyFilters(this.allProviders(), this.filterState));
|
|
220
|
+
}
|
|
221
|
+
onChangeProviderSearch(value: string): void {
|
|
222
|
+
this.filterState = { ...this.filterState, search: value };
|
|
223
|
+
this.showedProviders.set(applyFilters(this.allProviders(), this.filterState));
|
|
224
|
+
}
|
|
225
|
+
}
|