@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,104 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
|
|
2
|
+
import { $extension, $locale } from '@block-auth.io/core';
|
|
3
|
+
import { BtnBaseComponent } from '../../primitives/btn-base.component';
|
|
4
|
+
import { injectBlockAuthClient } from '../../provide-block-auth';
|
|
5
|
+
import { injectStore } from '../../inject-store';
|
|
6
|
+
|
|
7
|
+
const CHROME_STORE_URL =
|
|
8
|
+
'https://chromewebstore.google.com/detail/block-auth-one/jhjjpmjhiamddbfhogknjibkacajkpdg';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* El único botón que ramifica sobre el estado de la extensión (`$extension`,
|
|
12
|
+
* que no está envuelto por `BlockAuthService`, se lee aquí con `injectStore`).
|
|
13
|
+
* Según el estado (installed/isUser/isUserActive/isLocked):
|
|
14
|
+
* - no instalada → abre la Chrome Web Store,
|
|
15
|
+
* - no user / no activo / bloqueada → despacha el evento DOM `ext:open`,
|
|
16
|
+
* - conectable → `client.connectBlockAuth()`.
|
|
17
|
+
* El label sale del mapa localizado `uiConfig.config.content.btns`.
|
|
18
|
+
*/
|
|
19
|
+
@Component({
|
|
20
|
+
selector: 'ba-btn-block-auth',
|
|
21
|
+
standalone: true,
|
|
22
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
23
|
+
imports: [BtnBaseComponent],
|
|
24
|
+
template: `
|
|
25
|
+
@if (isDropdownProfile()) {
|
|
26
|
+
<button
|
|
27
|
+
id="btn-provider-blockauth-dropdown"
|
|
28
|
+
class="bas-h-full bas-w-full bas-border-0 bas-bg-transparent bas-p-0"
|
|
29
|
+
(click)="onBtnBlockAuthClicked()"
|
|
30
|
+
><ng-content /></button>
|
|
31
|
+
} @else {
|
|
32
|
+
<ba-btn-base
|
|
33
|
+
btnId="btn-provider-blockauth"
|
|
34
|
+
[text]="getBtnText() || text()"
|
|
35
|
+
type="button-outlined"
|
|
36
|
+
size="small"
|
|
37
|
+
paddingClassNames="bas-px-12"
|
|
38
|
+
[isIcon]="true"
|
|
39
|
+
textClassNames="bas-text-white bas-font-semibold bas-leading-6 bas-text-base"
|
|
40
|
+
[disabled]="disabled()"
|
|
41
|
+
[applyDisabledStyles]="false"
|
|
42
|
+
[isLoader]="false"
|
|
43
|
+
[isFocusOutlinedEnabled]="false"
|
|
44
|
+
(action)="onBtnBlockAuthClicked()"
|
|
45
|
+
>
|
|
46
|
+
<img
|
|
47
|
+
slot="icon"
|
|
48
|
+
src="https://dashboard.block-auth.io/img/blockauth_logo.png"
|
|
49
|
+
width="24"
|
|
50
|
+
height="24"
|
|
51
|
+
alt="Block-Auth"
|
|
52
|
+
/>
|
|
53
|
+
</ba-btn-base>
|
|
54
|
+
}
|
|
55
|
+
`,
|
|
56
|
+
})
|
|
57
|
+
export class BtnBlockAuthComponent {
|
|
58
|
+
isDropdownProfile = input(false);
|
|
59
|
+
disabled = input(false);
|
|
60
|
+
isEditable = input(false);
|
|
61
|
+
text = input('Conectar');
|
|
62
|
+
|
|
63
|
+
private client = injectBlockAuthClient();
|
|
64
|
+
private ui = injectStore(this.client.state.$ui);
|
|
65
|
+
private ws = injectStore(this.client.state.$ws);
|
|
66
|
+
private ext = injectStore($extension);
|
|
67
|
+
private locale = injectStore($locale);
|
|
68
|
+
|
|
69
|
+
private btnsTexts = computed(
|
|
70
|
+
() =>
|
|
71
|
+
(this.ui().uiConfig as
|
|
72
|
+
| { config?: { content?: { btns?: Record<string, Record<string, string>> } } }
|
|
73
|
+
| null)?.config?.content?.btns,
|
|
74
|
+
);
|
|
75
|
+
private txt = (key: string): string =>
|
|
76
|
+
this.btnsTexts()?.[key]?.[this.locale()] ?? this.text();
|
|
77
|
+
|
|
78
|
+
getBtnText = computed((): string => {
|
|
79
|
+
if (!(this.ws().ready && this.ui().isSdkSignedIn)) return '';
|
|
80
|
+
const ext = this.ext();
|
|
81
|
+
if (!ext.installed) return this.txt('download');
|
|
82
|
+
if (!ext.isUser) return this.txt('createAccount');
|
|
83
|
+
if (!ext.isUserActive) return this.txt('completeRegister');
|
|
84
|
+
if (ext.isLocked) return this.txt('unlockOtp');
|
|
85
|
+
return this.txt('connect');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
onBtnBlockAuthClicked(): void {
|
|
89
|
+
if (this.isEditable()) return;
|
|
90
|
+
if (!this.ws().ready || !this.ui().isSdkSignedIn) return;
|
|
91
|
+
const ext = this.ext();
|
|
92
|
+
if (!ext.installed) {
|
|
93
|
+
if (typeof window !== 'undefined') window.open(CHROME_STORE_URL, '_blank');
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (!ext.isUser || !ext.isUserActive || ext.isLocked) {
|
|
97
|
+
if (typeof document !== 'undefined') {
|
|
98
|
+
document.dispatchEvent(new CustomEvent('ext:open', { detail: {} }));
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.client.connectBlockAuth();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/angular';
|
|
3
|
+
import { BtnGmailComponent } from './btn-gmail.component';
|
|
4
|
+
import { provideBlockAuthClientForTest } from '../../testing';
|
|
5
|
+
|
|
6
|
+
describe('BtnGmail', () => {
|
|
7
|
+
it('compone BtnGoogleBase: al hacer click dispara oauth.signupGoogle', async () => {
|
|
8
|
+
const signupGoogleSpy = vi.fn(async () => undefined);
|
|
9
|
+
const fakeClient = { oauth: { signupGoogle: signupGoogleSpy } } as any;
|
|
10
|
+
await render(BtnGmailComponent, {
|
|
11
|
+
providers: [provideBlockAuthClientForTest(fakeClient)],
|
|
12
|
+
});
|
|
13
|
+
await fireEvent.click(screen.getByRole('button'));
|
|
14
|
+
await waitFor(() => expect(signupGoogleSpy).toHaveBeenCalledWith('auth-code'));
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
|
|
2
|
+
import { BtnGoogleBaseComponent } from './btn-google-base.component';
|
|
3
|
+
import { BiLogoGmailComponent } from '../../icons';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Envuelve `BtnGoogleBase` con la paleta roja de Gmail y el icono `BiLogoGmail`
|
|
7
|
+
* inline como icono por defecto.
|
|
8
|
+
*/
|
|
9
|
+
@Component({
|
|
10
|
+
selector: 'ba-btn-gmail',
|
|
11
|
+
standalone: true,
|
|
12
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
13
|
+
imports: [BtnGoogleBaseComponent, BiLogoGmailComponent],
|
|
14
|
+
template: `
|
|
15
|
+
<ba-btn-google-base
|
|
16
|
+
[isDropdownProfile]="isDropdownProfile()"
|
|
17
|
+
[text]="text()"
|
|
18
|
+
bgClassNames="bas-bg-[#ed2d2d] hover:bas-bg-[#eb1515]"
|
|
19
|
+
ringClassNames="bal-ba-hover-ring-red-400"
|
|
20
|
+
paddingClassNames="bas-px-12"
|
|
21
|
+
textClassNames="bas-text-white bas-font-semibold bas-leading-6 bas-text-base"
|
|
22
|
+
[iconEnabled]="iconEnabled()"
|
|
23
|
+
[disabled]="disabled()"
|
|
24
|
+
[isFocusOutlinedEnabled]="false"
|
|
25
|
+
[isEditable]="isEditable()"
|
|
26
|
+
(clicked)="clicked.emit()"
|
|
27
|
+
>
|
|
28
|
+
<ba-bi-logo-gmail slot="icon" class="bas-h-6 bas-w-6 bas-text-white" />
|
|
29
|
+
<ng-content />
|
|
30
|
+
</ba-btn-google-base>
|
|
31
|
+
`,
|
|
32
|
+
})
|
|
33
|
+
export class BtnGmailComponent {
|
|
34
|
+
isDropdownProfile = input(false);
|
|
35
|
+
text = input('Gmail');
|
|
36
|
+
iconEnabled = input(true);
|
|
37
|
+
disabled = input(false);
|
|
38
|
+
isEditable = input(false);
|
|
39
|
+
|
|
40
|
+
clicked = output<void>();
|
|
41
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/angular';
|
|
3
|
+
import { BtnGoogleBaseComponent } from './btn-google-base.component';
|
|
4
|
+
import { provideBlockAuthClientForTest } from '../../testing';
|
|
5
|
+
|
|
6
|
+
describe('BtnGoogleBase', () => {
|
|
7
|
+
it('al hacer click dispara client.oauth.signupGoogle("auth-code")', async () => {
|
|
8
|
+
const signupGoogleSpy = vi.fn(async () => undefined);
|
|
9
|
+
const fakeClient = { oauth: { signupGoogle: signupGoogleSpy } } as any;
|
|
10
|
+
await render(BtnGoogleBaseComponent, {
|
|
11
|
+
providers: [provideBlockAuthClientForTest(fakeClient)],
|
|
12
|
+
});
|
|
13
|
+
await fireEvent.click(screen.getByRole('button'));
|
|
14
|
+
await waitFor(() => expect(signupGoogleSpy).toHaveBeenCalledWith('auth-code'));
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
|
|
2
|
+
import { BtnBaseComponent } from '../../primitives/btn-base.component';
|
|
3
|
+
import { injectBlockAuthClient } from '../../provide-block-auth';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Botón Google base (icono + label). Toda la lógica OAuth vive en el core;
|
|
7
|
+
* aquí solo se renderiza y el click delega en `client.oauth.signupGoogle(mode)`.
|
|
8
|
+
* Los estilos son inputs reales (con los mismos defaults) para que `BtnGmail`
|
|
9
|
+
* pueda sobrescribirlos.
|
|
10
|
+
*
|
|
11
|
+
* El icono se proyecta con `[slot=icon]` y se re-proyecta hacia `BtnBase`
|
|
12
|
+
* (`ngProjectAs`).
|
|
13
|
+
*/
|
|
14
|
+
@Component({
|
|
15
|
+
selector: 'ba-btn-google-base',
|
|
16
|
+
standalone: true,
|
|
17
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
18
|
+
imports: [BtnBaseComponent],
|
|
19
|
+
template: `
|
|
20
|
+
@if (isDropdownProfile()) {
|
|
21
|
+
<button
|
|
22
|
+
id="btn-provider-google-dropdown"
|
|
23
|
+
class="bas-h-full bas-w-full bas-border-0 bas-bg-transparent bas-p-0"
|
|
24
|
+
(click)="onBtnGmailClicked()"
|
|
25
|
+
><ng-content /></button>
|
|
26
|
+
} @else {
|
|
27
|
+
<ba-btn-base
|
|
28
|
+
btnId="btn-provider-google"
|
|
29
|
+
[text]="text()"
|
|
30
|
+
type="button-outlined"
|
|
31
|
+
size="small"
|
|
32
|
+
[bgClassNames]="bgClassNames()"
|
|
33
|
+
[ringClassNames]="ringClassNames()"
|
|
34
|
+
[paddingClassNames]="paddingClassNames()"
|
|
35
|
+
[textClassNames]="textClassNames()"
|
|
36
|
+
[isIcon]="iconEnabled()"
|
|
37
|
+
[isFocusOutlinedEnabled]="isFocusOutlinedEnabled()"
|
|
38
|
+
[disabled]="disabled()"
|
|
39
|
+
(action)="onBtnGmailClicked()"
|
|
40
|
+
><ng-content select="[slot=icon]" ngProjectAs="[slot=icon]" /></ba-btn-base>
|
|
41
|
+
}
|
|
42
|
+
`,
|
|
43
|
+
})
|
|
44
|
+
export class BtnGoogleBaseComponent {
|
|
45
|
+
isDropdownProfile = input(false);
|
|
46
|
+
mode = input<'auth-code' | 'implicit'>('auth-code');
|
|
47
|
+
text = input('Google');
|
|
48
|
+
bgClassNames = input('bas-bg-[#ed2d2d] hover:bas-bg-[#eb1515]');
|
|
49
|
+
ringClassNames = input('bal-ba-hover-ring-red-400');
|
|
50
|
+
paddingClassNames = input('bas-px-12');
|
|
51
|
+
textClassNames = input('bas-text-white bas-font-semibold bas-leading-6 bas-text-base');
|
|
52
|
+
iconEnabled = input(false);
|
|
53
|
+
disabled = input(false);
|
|
54
|
+
isFocusOutlinedEnabled = input(false);
|
|
55
|
+
isEditable = input(false);
|
|
56
|
+
|
|
57
|
+
clicked = output<void>();
|
|
58
|
+
|
|
59
|
+
private client = injectBlockAuthClient();
|
|
60
|
+
|
|
61
|
+
async onBtnGmailClicked(): Promise<void> {
|
|
62
|
+
if (this.isEditable()) return;
|
|
63
|
+
try {
|
|
64
|
+
await this.client.oauth.signupGoogle(this.mode());
|
|
65
|
+
} catch (err) {
|
|
66
|
+
// Exponer la mala configuración (clientId ausente, script GIS no cargado)
|
|
67
|
+
// y las cancelaciones en vez de tragárnoslas: un catch silencioso haría
|
|
68
|
+
// que el botón pareciera inerte cuando en realidad falló.
|
|
69
|
+
console.warn('[blockauth] Google sign-in failed:', err);
|
|
70
|
+
}
|
|
71
|
+
this.clicked.emit();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -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 { BtnMetamaskComponent } from './btn-metamask.component';
|
|
5
|
+
import { provideBlockAuthClientForTest } from '../../testing';
|
|
6
|
+
|
|
7
|
+
describe('BtnMetamask', () => {
|
|
8
|
+
it('al hacer click dispara client.web3.signup("metamask")', async () => {
|
|
9
|
+
const signupSpy = vi.fn(async () => undefined);
|
|
10
|
+
const fakeClient = {
|
|
11
|
+
web3: { signup: signupSpy },
|
|
12
|
+
state: { $wallet: map({ address: null, isConnecting: false }) },
|
|
13
|
+
} as any;
|
|
14
|
+
await render(BtnMetamaskComponent, {
|
|
15
|
+
providers: [provideBlockAuthClientForTest(fakeClient)],
|
|
16
|
+
});
|
|
17
|
+
await fireEvent.click(screen.getByRole('button'));
|
|
18
|
+
await waitFor(() => expect(signupSpy).toHaveBeenCalledWith('metamask'));
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core';
|
|
2
|
+
import { BtnBaseComponent } from '../../primitives/btn-base.component';
|
|
3
|
+
import { injectBlockAuthClient } from '../../provide-block-auth';
|
|
4
|
+
import { injectStore } from '../../inject-store';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Botón de conexión MetaMask (EVM). El click delega en
|
|
8
|
+
* `client.web3.signup('metamask')`, que conecta la wallet inyectada y corre
|
|
9
|
+
* todo el handshake de signup en el core.
|
|
10
|
+
*/
|
|
11
|
+
const styles = {
|
|
12
|
+
bgClassNames: 'bas-bg-[#eb5900] hover:bas-bg-[#d85100]',
|
|
13
|
+
disabledBgClassNames: 'bas-bg-[rgba(235,89,0,0.75)] hover:bas-bg-[rgba(216,81,0,0.75)]',
|
|
14
|
+
ringClassNames: 'bal-ba-hover-ring-orange-400',
|
|
15
|
+
paddingClassNames: 'bas-px-12',
|
|
16
|
+
textClassNames: 'bas-text-white bas-font-semibold bas-leading-6 bas-text-base',
|
|
17
|
+
loaderWidth: 70,
|
|
18
|
+
loaderColor: '#fefefe',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
@Component({
|
|
22
|
+
selector: 'ba-btn-metamask',
|
|
23
|
+
standalone: true,
|
|
24
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
25
|
+
imports: [BtnBaseComponent],
|
|
26
|
+
template: `
|
|
27
|
+
@if (isDropdownProfile()) {
|
|
28
|
+
<button
|
|
29
|
+
id="btn-provider-metamask-dropdown"
|
|
30
|
+
class="bas-h-full bas-w-full bas-border-0 bas-bg-transparent bas-p-0"
|
|
31
|
+
(click)="onMetamaskSigning()"
|
|
32
|
+
><ng-content /></button>
|
|
33
|
+
} @else {
|
|
34
|
+
<ba-btn-base
|
|
35
|
+
btnId="btn-provider-metamask"
|
|
36
|
+
[text]="btnText()"
|
|
37
|
+
type="button-outlined"
|
|
38
|
+
size="small"
|
|
39
|
+
[bgClassNames]="wallet().isConnecting ? disabledBg : bg"
|
|
40
|
+
[ringClassNames]="ring"
|
|
41
|
+
[paddingClassNames]="padding"
|
|
42
|
+
[textClassNames]="textCls"
|
|
43
|
+
[isLoader]="wallet().isConnecting"
|
|
44
|
+
[loaderWidth]="loaderWidth"
|
|
45
|
+
[loaderColor]="loaderColor"
|
|
46
|
+
[isIcon]="iconEnabled()"
|
|
47
|
+
[isFocusOutlinedEnabled]="false"
|
|
48
|
+
[disabled]="disabled()"
|
|
49
|
+
(action)="onMetamaskSigning()"
|
|
50
|
+
>
|
|
51
|
+
<img
|
|
52
|
+
slot="icon"
|
|
53
|
+
src="https://img.icons8.com/ios-filled/48/ffffff/metamask-logo.png"
|
|
54
|
+
height="24"
|
|
55
|
+
width="24"
|
|
56
|
+
alt="MetaMask"
|
|
57
|
+
/>
|
|
58
|
+
</ba-btn-base>
|
|
59
|
+
}
|
|
60
|
+
`,
|
|
61
|
+
})
|
|
62
|
+
export class BtnMetamaskComponent {
|
|
63
|
+
isDropdownProfile = input(false);
|
|
64
|
+
text = input('MetaMask');
|
|
65
|
+
isTextInsteadOfAccount = input(false);
|
|
66
|
+
iconEnabled = input(true);
|
|
67
|
+
disabled = input(false);
|
|
68
|
+
isEditable = input(false);
|
|
69
|
+
|
|
70
|
+
clicked = output<void>();
|
|
71
|
+
|
|
72
|
+
private client = injectBlockAuthClient();
|
|
73
|
+
wallet = injectStore(this.client.state.$wallet);
|
|
74
|
+
|
|
75
|
+
bg = styles.bgClassNames;
|
|
76
|
+
disabledBg = styles.disabledBgClassNames;
|
|
77
|
+
ring = styles.ringClassNames;
|
|
78
|
+
padding = styles.paddingClassNames;
|
|
79
|
+
textCls = styles.textClassNames;
|
|
80
|
+
loaderWidth = styles.loaderWidth;
|
|
81
|
+
loaderColor = styles.loaderColor;
|
|
82
|
+
|
|
83
|
+
btnText = computed(() => {
|
|
84
|
+
const short = (a: string | null) => (a ? `${a.slice(0, 6)}…${a.slice(-4)}` : '');
|
|
85
|
+
return this.isTextInsteadOfAccount() ? this.text() : short(this.wallet().address) || this.text();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
async onMetamaskSigning(): Promise<void> {
|
|
89
|
+
if (this.isEditable()) return;
|
|
90
|
+
if (this.wallet().isConnecting) return;
|
|
91
|
+
try {
|
|
92
|
+
await this.client.web3.signup('metamask');
|
|
93
|
+
} catch {
|
|
94
|
+
/* conexión/firma cancelada o fallida */
|
|
95
|
+
}
|
|
96
|
+
this.clicked.emit();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/angular';
|
|
3
|
+
import { BtnMicrosoftComponent } from './btn-microsoft.component';
|
|
4
|
+
import { provideBlockAuthClientForTest } from '../../testing';
|
|
5
|
+
|
|
6
|
+
describe('BtnMicrosoft', () => {
|
|
7
|
+
it('al hacer click dispara client.oauth.signupMicrosoft()', async () => {
|
|
8
|
+
const signupMicrosoftSpy = vi.fn(async () => undefined);
|
|
9
|
+
const fakeClient = { oauth: { signupMicrosoft: signupMicrosoftSpy } } as any;
|
|
10
|
+
await render(BtnMicrosoftComponent, {
|
|
11
|
+
providers: [provideBlockAuthClientForTest(fakeClient)],
|
|
12
|
+
});
|
|
13
|
+
await fireEvent.click(screen.getByRole('button'));
|
|
14
|
+
await waitFor(() => expect(signupMicrosoftSpy).toHaveBeenCalled());
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
|
|
2
|
+
import { BtnBaseComponent } from '../../primitives/btn-base.component';
|
|
3
|
+
import { injectBlockAuthClient } from '../../provide-block-auth';
|
|
4
|
+
import { TiVendorMicrosoftComponent } from '../../icons';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Botón Microsoft (glifo + label). El click delega en
|
|
8
|
+
* `client.oauth.signupMicrosoft()` (la config MSAL + el handshake de signup
|
|
9
|
+
* viven en el core).
|
|
10
|
+
*/
|
|
11
|
+
const msStyles = {
|
|
12
|
+
bgClassNames: 'bas-bg-[#2b88d8] hover:bas-bg-[#0078d4]',
|
|
13
|
+
ringClassNames: 'bal-ba-hover-ring-blue-400',
|
|
14
|
+
textClassNames: 'bas-text-white bas-font-semibold bas-leading-6 bas-text-base',
|
|
15
|
+
paddingClassNames: 'bas-px-12',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
@Component({
|
|
19
|
+
selector: 'ba-btn-microsoft',
|
|
20
|
+
standalone: true,
|
|
21
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
22
|
+
imports: [BtnBaseComponent, TiVendorMicrosoftComponent],
|
|
23
|
+
template: `
|
|
24
|
+
@if (isDropdownProfile()) {
|
|
25
|
+
<button
|
|
26
|
+
id="btn-provider-microsoft-dropdown"
|
|
27
|
+
class="bas-h-full bas-w-full bas-border-0 bas-bg-transparent bas-p-0"
|
|
28
|
+
(click)="microsoftSignin()"
|
|
29
|
+
><ng-content /></button>
|
|
30
|
+
} @else {
|
|
31
|
+
<ba-btn-base
|
|
32
|
+
btnId="btn-provider-microsoft"
|
|
33
|
+
[text]="text()"
|
|
34
|
+
type="button-outlined"
|
|
35
|
+
size="small"
|
|
36
|
+
[bgClassNames]="bg"
|
|
37
|
+
[ringClassNames]="ring"
|
|
38
|
+
[paddingClassNames]="padding"
|
|
39
|
+
[textClassNames]="textCls"
|
|
40
|
+
[isIcon]="iconEnabled()"
|
|
41
|
+
[isFocusOutlinedEnabled]="false"
|
|
42
|
+
[disabled]="disabled()"
|
|
43
|
+
(action)="microsoftSignin()"
|
|
44
|
+
>
|
|
45
|
+
<ba-ti-vendor-microsoft slot="icon" class="bas-h-6 bas-w-6 bas-text-white" />
|
|
46
|
+
</ba-btn-base>
|
|
47
|
+
}
|
|
48
|
+
`,
|
|
49
|
+
})
|
|
50
|
+
export class BtnMicrosoftComponent {
|
|
51
|
+
isDropdownProfile = input(false);
|
|
52
|
+
text = input('Microsoft');
|
|
53
|
+
iconEnabled = input(true);
|
|
54
|
+
disabled = input(false);
|
|
55
|
+
isEditable = input(false);
|
|
56
|
+
|
|
57
|
+
clicked = output<void>();
|
|
58
|
+
|
|
59
|
+
private client = injectBlockAuthClient();
|
|
60
|
+
|
|
61
|
+
bg = msStyles.bgClassNames;
|
|
62
|
+
ring = msStyles.ringClassNames;
|
|
63
|
+
padding = msStyles.paddingClassNames;
|
|
64
|
+
textCls = msStyles.textClassNames;
|
|
65
|
+
|
|
66
|
+
async microsoftSignin(): Promise<void> {
|
|
67
|
+
if (this.isEditable()) return;
|
|
68
|
+
try {
|
|
69
|
+
await this.client.oauth.signupMicrosoft();
|
|
70
|
+
} catch (err) {
|
|
71
|
+
// Exponer mala configuración (clientId ausente, errores MSAL) y
|
|
72
|
+
// cancelaciones en vez de tragárnoslas.
|
|
73
|
+
console.warn('[blockauth] Microsoft sign-in failed:', err);
|
|
74
|
+
}
|
|
75
|
+
this.clicked.emit();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -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 { BtnPhantomComponent } from './btn-phantom.component';
|
|
5
|
+
import { provideBlockAuthClientForTest } from '../../testing';
|
|
6
|
+
|
|
7
|
+
describe('BtnPhantom', () => {
|
|
8
|
+
it('al hacer click dispara client.web3.signup("phantom")', async () => {
|
|
9
|
+
const signupSpy = vi.fn(async () => undefined);
|
|
10
|
+
const fakeClient = {
|
|
11
|
+
web3: { signup: signupSpy },
|
|
12
|
+
state: { $wallet: map({ address: null, isConnecting: false }) },
|
|
13
|
+
} as any;
|
|
14
|
+
await render(BtnPhantomComponent, {
|
|
15
|
+
providers: [provideBlockAuthClientForTest(fakeClient)],
|
|
16
|
+
});
|
|
17
|
+
await fireEvent.click(screen.getByRole('button'));
|
|
18
|
+
await waitFor(() => expect(signupSpy).toHaveBeenCalledWith('phantom'));
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, input, output } from '@angular/core';
|
|
2
|
+
import { BtnBaseComponent } from '../../primitives/btn-base.component';
|
|
3
|
+
import { imgUrlByService } from '../../primitives/helpers';
|
|
4
|
+
import { injectBlockAuthClient } from '../../provide-block-auth';
|
|
5
|
+
import { injectStore } from '../../inject-store';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Botón de conexión Phantom (Solana). El click delega en
|
|
9
|
+
* `client.web3.signup('phantom')`.
|
|
10
|
+
*/
|
|
11
|
+
const styles = {
|
|
12
|
+
bgClassNames: 'bas-bg-[#9092e7] hover:bas-bg-[#7b7de2]',
|
|
13
|
+
disabledBgClassNames: 'bas-bg-[rgba(144,146,231,0.75)] hover:bas-bg-[rgba(123,125,226,0.75)]',
|
|
14
|
+
ringClassNames: 'bal-ba-hover-ring-puple-400',
|
|
15
|
+
paddingClassNames: 'bas-px-12',
|
|
16
|
+
textClassNames: 'bas-text-white bas-font-semibold bas-leading-6 bas-text-base',
|
|
17
|
+
loaderWidth: 70,
|
|
18
|
+
loaderColor: '#fefefe',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
@Component({
|
|
22
|
+
selector: 'ba-btn-phantom',
|
|
23
|
+
standalone: true,
|
|
24
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
25
|
+
imports: [BtnBaseComponent],
|
|
26
|
+
template: `
|
|
27
|
+
@if (isDropdownProfile()) {
|
|
28
|
+
<button
|
|
29
|
+
id="btn-provider-phantom-dropdown"
|
|
30
|
+
class="bas-h-full bas-w-full bas-border-0 bas-bg-transparent bas-p-0"
|
|
31
|
+
(click)="onPhantomSignin()"
|
|
32
|
+
><ng-content /></button>
|
|
33
|
+
} @else {
|
|
34
|
+
<ba-btn-base
|
|
35
|
+
btnId="btn-provider-phantom"
|
|
36
|
+
[text]="btnText()"
|
|
37
|
+
type="button-outlined"
|
|
38
|
+
size="small"
|
|
39
|
+
[bgClassNames]="wallet().isConnecting ? disabledBg : bg"
|
|
40
|
+
[ringClassNames]="ring"
|
|
41
|
+
[paddingClassNames]="padding"
|
|
42
|
+
[textClassNames]="textCls"
|
|
43
|
+
[isLoader]="wallet().isConnecting"
|
|
44
|
+
[loaderWidth]="loaderWidth"
|
|
45
|
+
[loaderColor]="loaderColor"
|
|
46
|
+
[isIcon]="iconEnabled()"
|
|
47
|
+
[isFocusOutlinedEnabled]="false"
|
|
48
|
+
[disabled]="disabled()"
|
|
49
|
+
(action)="onPhantomSignin()"
|
|
50
|
+
>
|
|
51
|
+
<img slot="icon" [src]="phantomIconSrc" height="24" width="24" alt="Phantom" />
|
|
52
|
+
</ba-btn-base>
|
|
53
|
+
}
|
|
54
|
+
`,
|
|
55
|
+
})
|
|
56
|
+
export class BtnPhantomComponent {
|
|
57
|
+
isDropdownProfile = input(false);
|
|
58
|
+
text = input('Phantom');
|
|
59
|
+
isTextInsteadOfAccount = input(false);
|
|
60
|
+
iconEnabled = input(true);
|
|
61
|
+
disabled = input(false);
|
|
62
|
+
isEditable = input(false);
|
|
63
|
+
|
|
64
|
+
clicked = output<void>();
|
|
65
|
+
|
|
66
|
+
private client = injectBlockAuthClient();
|
|
67
|
+
wallet = injectStore(this.client.state.$wallet);
|
|
68
|
+
|
|
69
|
+
bg = styles.bgClassNames;
|
|
70
|
+
disabledBg = styles.disabledBgClassNames;
|
|
71
|
+
ring = styles.ringClassNames;
|
|
72
|
+
padding = styles.paddingClassNames;
|
|
73
|
+
textCls = styles.textClassNames;
|
|
74
|
+
loaderWidth = styles.loaderWidth;
|
|
75
|
+
loaderColor = styles.loaderColor;
|
|
76
|
+
phantomIconSrc = imgUrlByService('phantom-white');
|
|
77
|
+
|
|
78
|
+
btnText = computed(() => {
|
|
79
|
+
const short = (a: string | null) => (a ? `${a.slice(0, 6)}…${a.slice(-4)}` : '');
|
|
80
|
+
return this.isTextInsteadOfAccount() ? this.text() : short(this.wallet().address) || this.text();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
async onPhantomSignin(): Promise<void> {
|
|
84
|
+
if (this.isEditable()) return;
|
|
85
|
+
if (this.wallet().isConnecting) return;
|
|
86
|
+
try {
|
|
87
|
+
await this.client.web3.signup('phantom');
|
|
88
|
+
} catch {
|
|
89
|
+
/* conexión/firma cancelada o fallida */
|
|
90
|
+
}
|
|
91
|
+
this.clicked.emit();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { Component } from '@angular/core';
|
|
3
|
+
import { render, screen, fireEvent } from '@testing-library/angular';
|
|
4
|
+
import { map } from 'nanostores';
|
|
5
|
+
import { BtnSigninComponent } from './btn-signin.component';
|
|
6
|
+
import { provideBlockAuthClientForTest } from '../../testing';
|
|
7
|
+
|
|
8
|
+
function makeClient(ui: Record<string, unknown> = {}) {
|
|
9
|
+
const requestSignin = vi.fn();
|
|
10
|
+
const openModal = vi.fn();
|
|
11
|
+
const $ui = map({ isSigning: false, serviceName: null, uiConfig: null, ...ui });
|
|
12
|
+
const client = { requestSignin, openModal, state: { $ui } } as any;
|
|
13
|
+
return { client, requestSignin, openModal, $ui };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('BtnSignin', () => {
|
|
17
|
+
it('renderiza con el id correcto', async () => {
|
|
18
|
+
const { client } = makeClient();
|
|
19
|
+
await render(BtnSigninComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
20
|
+
expect(screen.getByRole('button').id).toBe('btn-login-signup');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('al hacer click llama requestSignin y NO openModal si enabled es falsy', async () => {
|
|
24
|
+
const { client, requestSignin, openModal } = makeClient();
|
|
25
|
+
await render(BtnSigninComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
26
|
+
await fireEvent.click(screen.getByRole('button'));
|
|
27
|
+
expect(requestSignin).toHaveBeenCalledOnce();
|
|
28
|
+
expect(openModal).not.toHaveBeenCalled();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('abre el modal signin si uiConfig.config[serviceName].enabled es true', async () => {
|
|
32
|
+
const { client, requestSignin, openModal } = makeClient({
|
|
33
|
+
serviceName: 'blockauth',
|
|
34
|
+
uiConfig: { config: { blockauth: { enabled: true } } },
|
|
35
|
+
});
|
|
36
|
+
await render(BtnSigninComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
37
|
+
await fireEvent.click(screen.getByRole('button'));
|
|
38
|
+
expect(requestSignin).toHaveBeenCalled();
|
|
39
|
+
expect(openModal).toHaveBeenCalledWith('signin');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('no llama requestSignin ni openModal si isEditable es true', async () => {
|
|
43
|
+
const { client, requestSignin, openModal } = makeClient();
|
|
44
|
+
@Component({
|
|
45
|
+
standalone: true,
|
|
46
|
+
imports: [BtnSigninComponent],
|
|
47
|
+
template: `<ba-btn-signin [isEditable]="true" />`,
|
|
48
|
+
})
|
|
49
|
+
class HostComponent {}
|
|
50
|
+
await render(HostComponent, { providers: [provideBlockAuthClientForTest(client)] });
|
|
51
|
+
await fireEvent.click(screen.getByRole('button'));
|
|
52
|
+
expect(requestSignin).not.toHaveBeenCalled();
|
|
53
|
+
expect(openModal).not.toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
});
|