@block-auth.io/angular 0.1.1 → 0.1.2
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 +7 -7
- package/.turbo/turbo-lint.log +4 -4
- package/.turbo/turbo-test.log +1037 -82
- package/.turbo/turbo-typecheck.log +19 -4
- package/CHANGELOG.md +7 -0
- package/package.json +2 -2
- package/src/lib/components/txns/internals/permissions-required-modal.component.test.ts +16 -3
- package/src/lib/components/txns/internals/permissions-required-modal.component.ts +24 -16
- package/src/lib/components/txns/internals/solicited-data-modal.component.test.ts +37 -5
- package/src/lib/components/txns/internals/solicited-data-modal.component.ts +98 -65
- package/src/lib/components/txns/internals/txn-error.component.test.ts +33 -6
- package/src/lib/components/txns/internals/txn-error.component.ts +52 -29
- package/src/lib/components/txns/internals/txn.component.test.ts +63 -16
- package/src/lib/components/txns/internals/txn.component.ts +141 -61
- package/src/lib/components/txns/txns-overview.component.ts +109 -91
|
@@ -1,4 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
> @block-auth.io/angular@0.1.
|
|
3
|
-
>
|
|
4
|
-
|
|
1
|
+
|
|
2
|
+
> @block-auth.io/angular@0.1.2 pretypecheck /home/vlad/Escritorio/blockauth-code/blockauth-sdk/packages/angular
|
|
3
|
+
> pnpm build:css
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
> @block-auth.io/angular@0.1.2 build:css /home/vlad/Escritorio/blockauth-code/blockauth-sdk/packages/angular
|
|
7
|
+
> tailwindcss -i ../ui-styles/src/input.css -c ./tailwind.config.cjs -o ./src/styles.generated.css --minify && node scripts/css-to-ts.mjs
|
|
8
|
+
|
|
9
|
+
Browserslist: caniuse-lite is outdated. Please run:
|
|
10
|
+
npx update-browserslist-db@latest
|
|
11
|
+
Why you should do it regularly: https://github.com/browserslist/update-db#readme
|
|
12
|
+
|
|
13
|
+
Rebuilding...
|
|
14
|
+
|
|
15
|
+
Done in 1039ms.
|
|
16
|
+
|
|
17
|
+
> @block-auth.io/angular@0.1.2 typecheck /home/vlad/Escritorio/blockauth-code/blockauth-sdk/packages/angular
|
|
18
|
+
> tsc -p tsconfig.lib.json --noEmit
|
|
19
|
+
|
package/CHANGELOG.md
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@block-auth.io/angular",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"nanostores": "^0.10.0",
|
|
6
6
|
"tslib": "^2.6.0",
|
|
7
|
-
"@block-auth.io/core": "0.4.
|
|
7
|
+
"@block-auth.io/core": "0.4.1",
|
|
8
8
|
"@block-auth.io/ui-styles": "0.4.0"
|
|
9
9
|
},
|
|
10
10
|
"peerDependencies": {
|
|
@@ -1,14 +1,27 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
2
|
import { render, screen } from '@testing-library/angular';
|
|
3
|
+
import { setLocale } from '@block-auth.io/core';
|
|
3
4
|
import { PermissionsRequiredModalComponent } from './permissions-required-modal.component';
|
|
4
5
|
|
|
5
6
|
// Port de packages/react/src/components/txns/internals/PermissionsRequiredModal.test.tsx
|
|
6
7
|
// (mapa: packages/vue/.../PermissionsRequiredModal.test.ts).
|
|
7
8
|
describe('PermissionsRequiredModal', () => {
|
|
9
|
+
afterEach(() => setLocale('en'));
|
|
10
|
+
|
|
8
11
|
it('renderiza el modal de permisos requeridos', async () => {
|
|
9
12
|
await render(PermissionsRequiredModalComponent);
|
|
10
13
|
expect(screen.getByText('Temporal access to identity')).toBeDefined();
|
|
11
|
-
expect(
|
|
12
|
-
|
|
14
|
+
expect(
|
|
15
|
+
screen.getByText('Access to the list of authorised apps'),
|
|
16
|
+
).toBeDefined();
|
|
17
|
+
expect(
|
|
18
|
+
screen.getByText('Only this transaction will be signed'),
|
|
19
|
+
).toBeDefined();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('respeta el locale activo', async () => {
|
|
23
|
+
setLocale('es');
|
|
24
|
+
await render(PermissionsRequiredModalComponent);
|
|
25
|
+
expect(screen.getByText('Acceso temporal a tu identidad')).toBeDefined();
|
|
13
26
|
});
|
|
14
27
|
});
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed } from '@angular/core';
|
|
2
|
+
import { $locale, t as translate, type TextKey } from '@block-auth.io/core';
|
|
3
|
+
import { injectStore } from '../../../inject-store';
|
|
2
4
|
|
|
3
5
|
// Port literal de packages/react/src/components/txns/internals/PermissionsRequiredModal.tsx
|
|
4
|
-
// (mapa: packages/vue/.../PermissionsRequiredModal.vue). Sin props/outputs
|
|
5
|
-
// lógica de core: solo itera un array fijo de permisos.
|
|
6
|
+
// (mapa: packages/vue/.../PermissionsRequiredModal.vue). Sin props/outputs.
|
|
6
7
|
const iconClassNames = 'bas-w-6 bas-h-6 bas-text-[#009F6B]';
|
|
7
|
-
const requiredPermissions = [
|
|
8
|
-
{ message: 'Temporal access to identity', icon: '👤' },
|
|
9
|
-
{ message: 'Access to the list of authorised apps', icon: '📝' },
|
|
10
|
-
{ message: 'Only this transaction will be signed', icon: '🔄' },
|
|
11
|
-
];
|
|
12
8
|
|
|
13
9
|
@Component({
|
|
14
10
|
selector: 'ba-permissions-required-modal',
|
|
@@ -18,18 +14,30 @@ const requiredPermissions = [
|
|
|
18
14
|
<div
|
|
19
15
|
class="bal-ba-transition-2 bas-box-border bas-grid bas-w-[334px] bas-grid-rows-3 bas-gap-3 bas-rounded-md bas-border-2 bas-border-solid bas-border-[var(--bal-primary-100)] bas-bg-[var(--bal-primary-10)] bas-py-4 bas-pl-3 hover:bas-border-[var(--bal-primary-300)]"
|
|
20
16
|
>
|
|
21
|
-
@for (permission of requiredPermissions; track $index) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
17
|
+
@for (permission of requiredPermissions(); track $index) {
|
|
18
|
+
<div class="bas-flex bas-flex-row bas-items-center bas-gap-2">
|
|
19
|
+
<span [class]="iconClassNames" aria-hidden="true">{{
|
|
20
|
+
permission.icon
|
|
21
|
+
}}</span>
|
|
22
|
+
<p
|
|
23
|
+
class="bas-m-0 bas-text-start bas-text-base bas-text-[var(--bal-primary-800)]"
|
|
24
|
+
>
|
|
25
|
+
{{ permission.message }}
|
|
26
|
+
</p>
|
|
27
|
+
</div>
|
|
28
28
|
}
|
|
29
29
|
</div>
|
|
30
30
|
`,
|
|
31
31
|
})
|
|
32
32
|
export class PermissionsRequiredModalComponent {
|
|
33
33
|
protected readonly iconClassNames = iconClassNames;
|
|
34
|
-
|
|
34
|
+
|
|
35
|
+
private readonly locale = injectStore($locale);
|
|
36
|
+
protected t = (key: TextKey): string => translate(key, this.locale());
|
|
37
|
+
|
|
38
|
+
protected readonly requiredPermissions = computed(() => [
|
|
39
|
+
{ message: this.t('permTemporalIdentity'), icon: '👤' },
|
|
40
|
+
{ message: this.t('permAuthorisedApps'), icon: '📝' },
|
|
41
|
+
{ message: this.t('permOnlyThisTxn'), icon: '🔄' },
|
|
42
|
+
]);
|
|
35
43
|
}
|
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
2
|
import { Component } from '@angular/core';
|
|
3
3
|
import { render, screen, fireEvent } from '@testing-library/angular';
|
|
4
|
-
import {
|
|
4
|
+
import { setLocale } from '@block-auth.io/core';
|
|
5
|
+
import {
|
|
6
|
+
SolicitedDataModalComponent,
|
|
7
|
+
type ParamChange,
|
|
8
|
+
} from './solicited-data-modal.component';
|
|
5
9
|
|
|
6
10
|
// Port de packages/react/src/components/txns/internals/SolicitedDataModal.test.tsx
|
|
7
11
|
// (mapa: packages/vue/.../SolicitedDataModal.test.ts). Presentacional, sin client.
|
|
8
12
|
describe('SolicitedDataModal', () => {
|
|
13
|
+
afterEach(() => setLocale('en'));
|
|
14
|
+
|
|
9
15
|
it('renderiza el modal de datos solicitados con campos requeridos y opcionales', async () => {
|
|
10
16
|
@Component({
|
|
11
17
|
standalone: true,
|
|
12
18
|
imports: [SolicitedDataModalComponent],
|
|
13
|
-
template: `<ba-solicited-data-modal
|
|
19
|
+
template: `<ba-solicited-data-modal
|
|
20
|
+
[requiredParams]="['users-email']"
|
|
21
|
+
[optionalParams]="['users-phone']"
|
|
22
|
+
/>`,
|
|
14
23
|
})
|
|
15
24
|
class Host {}
|
|
16
25
|
await render(Host);
|
|
@@ -18,6 +27,24 @@ describe('SolicitedDataModal', () => {
|
|
|
18
27
|
expect(screen.getByText('Phone')).toBeDefined();
|
|
19
28
|
});
|
|
20
29
|
|
|
30
|
+
it('respeta el locale activo', async () => {
|
|
31
|
+
setLocale('es');
|
|
32
|
+
@Component({
|
|
33
|
+
standalone: true,
|
|
34
|
+
imports: [SolicitedDataModalComponent],
|
|
35
|
+
template: `<ba-solicited-data-modal
|
|
36
|
+
[requiredParams]="['users-email']"
|
|
37
|
+
[optionalParams]="['users-phone']"
|
|
38
|
+
/>`,
|
|
39
|
+
})
|
|
40
|
+
class Host {}
|
|
41
|
+
await render(Host);
|
|
42
|
+
expect(screen.getByText('Correo electrónico')).toBeDefined();
|
|
43
|
+
expect(screen.getByText('Teléfono')).toBeDefined();
|
|
44
|
+
expect(screen.getByText('Requerido')).toBeDefined();
|
|
45
|
+
expect(screen.getByText('Opcional')).toBeDefined();
|
|
46
|
+
});
|
|
47
|
+
|
|
21
48
|
it('emite paramChange al escribir y validationChange según validez', async () => {
|
|
22
49
|
const paramChanges: ParamChange[] = [];
|
|
23
50
|
const validationChanges: boolean[] = [];
|
|
@@ -44,7 +71,10 @@ describe('SolicitedDataModal', () => {
|
|
|
44
71
|
|
|
45
72
|
const input = screen.getByRole('textbox');
|
|
46
73
|
await fireEvent.input(input, { target: { value: 'not-an-email' } });
|
|
47
|
-
expect(paramChanges[paramChanges.length - 1]).toEqual({
|
|
74
|
+
expect(paramChanges[paramChanges.length - 1]).toEqual({
|
|
75
|
+
slug: 'users-email',
|
|
76
|
+
value: 'not-an-email',
|
|
77
|
+
});
|
|
48
78
|
|
|
49
79
|
fixture.componentInstance.vals = { 'users-email': 'a@b.com' };
|
|
50
80
|
fixture.detectChanges();
|
|
@@ -67,6 +97,8 @@ describe('SolicitedDataModal', () => {
|
|
|
67
97
|
await render(Host);
|
|
68
98
|
const input = screen.getByRole('textbox');
|
|
69
99
|
await fireEvent.blur(input);
|
|
70
|
-
expect(
|
|
100
|
+
expect(
|
|
101
|
+
screen.getByText('Enter a valid email (e.g. name@domain.com)'),
|
|
102
|
+
).toBeDefined();
|
|
71
103
|
});
|
|
72
104
|
});
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
output,
|
|
8
8
|
signal,
|
|
9
9
|
} from '@angular/core';
|
|
10
|
+
import { $locale, t as translate, type TextKey } from '@block-auth.io/core';
|
|
11
|
+
import { injectStore } from '../../../inject-store';
|
|
10
12
|
|
|
11
13
|
// `handleParamChange`/`onValidationChange` (callback props en react) -> outputs
|
|
12
14
|
// `paramChange`/`validationChange`: notificaciones puras de "algo cambió en la
|
|
@@ -22,13 +24,12 @@ export interface ParamChange {
|
|
|
22
24
|
value: string;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
const
|
|
26
|
-
'
|
|
27
|
-
'users-
|
|
28
|
-
'users-
|
|
29
|
-
'users-
|
|
30
|
-
'users-
|
|
31
|
-
'users-phone': 'Phone',
|
|
27
|
+
const PARAM_LABEL_KEYS: Record<string, TextKey> = {
|
|
28
|
+
'users-firstname': 'firstName',
|
|
29
|
+
'users-lastname': 'lastName',
|
|
30
|
+
'users-secondlastname': 'secondLastName',
|
|
31
|
+
'users-email': 'email',
|
|
32
|
+
'users-phone': 'phone',
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
const PARAM_DISPLAY_ORDER = [
|
|
@@ -56,12 +57,12 @@ const PARAM_VALIDATORS: Record<string, (v: string) => boolean> = {
|
|
|
56
57
|
'users-phone': (v) => /^\+?[\d\s\-().]{6,20}$/.test(v.trim()),
|
|
57
58
|
};
|
|
58
59
|
|
|
59
|
-
const
|
|
60
|
-
'users-firstname': '
|
|
61
|
-
'users-lastname': '
|
|
62
|
-
'users-secondlastname': '
|
|
63
|
-
'users-email': '
|
|
64
|
-
'users-phone': '
|
|
60
|
+
const PARAM_ERROR_KEYS: Record<string, TextKey> = {
|
|
61
|
+
'users-firstname': 'onlyLettersAllowed',
|
|
62
|
+
'users-lastname': 'onlyLettersAllowed',
|
|
63
|
+
'users-secondlastname': 'onlyLettersAllowed',
|
|
64
|
+
'users-email': 'enterValidEmail',
|
|
65
|
+
'users-phone': 'enterValidPhone',
|
|
65
66
|
};
|
|
66
67
|
|
|
67
68
|
@Component({
|
|
@@ -73,54 +74,66 @@ const PARAM_ERROR_MSG: Record<string, string> = {
|
|
|
73
74
|
class="bal-ba-transition-2 bas-box-border bas-flex bas-w-[334px] bas-flex-col bas-gap-3 bas-rounded-md bas-border-2 bas-border-solid bas-border-[var(--bal-primary-100)] bas-bg-[var(--bal-primary-10)] bas-px-4 bas-py-4 hover:bas-border-[var(--bal-primary-300)]"
|
|
74
75
|
>
|
|
75
76
|
@for (slug of visibleParams(); track slug) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
>
|
|
111
|
-
<path d="M12 2 1 21h22L12 2Zm0 6c.55 0 1 .45 1 1v5a1 1 0 1 1-2 0V9c0-.55.45-1 1-1Zm0 9.5a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Z" />
|
|
112
|
-
</svg>
|
|
113
|
-
}
|
|
114
|
-
</div>
|
|
77
|
+
<div class="bas-flex bas-flex-col bas-gap-1">
|
|
78
|
+
<div class="bas-flex bas-items-center bas-justify-between">
|
|
79
|
+
<label class="bas-text-xs bas-font-medium bas-text-slate-600">{{
|
|
80
|
+
label(slug)
|
|
81
|
+
}}</label>
|
|
82
|
+
<span
|
|
83
|
+
style="font-size: 10px"
|
|
84
|
+
[class]="
|
|
85
|
+
'bas-rounded bas-px-1 bas-py-px bas-font-bold bas-uppercase bas-tracking-wide ' +
|
|
86
|
+
(isRequired(slug)
|
|
87
|
+
? 'bas-bg-amber-100 bas-text-amber-600'
|
|
88
|
+
: 'bas-bg-slate-100 bas-text-slate-400')
|
|
89
|
+
"
|
|
90
|
+
>
|
|
91
|
+
{{
|
|
92
|
+
isRequired(slug)
|
|
93
|
+
? requiredLabelTxtResolved()
|
|
94
|
+
: optionalLabelTxtResolved()
|
|
95
|
+
}}
|
|
96
|
+
</span>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="bas-relative">
|
|
99
|
+
<input
|
|
100
|
+
[type]="inputType(slug)"
|
|
101
|
+
[value]="valueOf(slug)"
|
|
102
|
+
[class]="
|
|
103
|
+
'bas-w-full bas-rounded bas-border bas-bg-white bas-px-2 bas-py-1 bas-text-sm bas-text-slate-700 bas-outline-none bas-transition-colors ' +
|
|
104
|
+
(errorOf(slug)
|
|
105
|
+
? 'bas-border-red-400 bas-pr-7 focus:bas-border-red-500'
|
|
106
|
+
: 'bas-border-slate-300 focus:bas-border-[var(--bal-primary-500)]')
|
|
107
|
+
"
|
|
108
|
+
(input)="onInput(slug, $event)"
|
|
109
|
+
(blur)="onBlur(slug)"
|
|
110
|
+
/>
|
|
115
111
|
@if (errorOf(slug)) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
112
|
+
<svg
|
|
113
|
+
class="bas-absolute bas-right-2 bas-top-1/2 bas-h-4 bas-w-4 -bas-translate-y-1/2 bas-text-red-500"
|
|
114
|
+
viewBox="0 0 24 24"
|
|
115
|
+
fill="currentColor"
|
|
116
|
+
aria-hidden="true"
|
|
117
|
+
>
|
|
118
|
+
<path
|
|
119
|
+
d="M12 2 1 21h22L12 2Zm0 6c.55 0 1 .45 1 1v5a1 1 0 1 1-2 0V9c0-.55.45-1 1-1Zm0 9.5a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Z"
|
|
120
|
+
/>
|
|
121
|
+
</svg>
|
|
119
122
|
}
|
|
120
123
|
</div>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
@if (errorOf(slug)) {
|
|
125
|
+
<p
|
|
126
|
+
style="font-size: 10px"
|
|
127
|
+
class="bas-m-0 bas-px-2 bas-font-medium bas-text-red-500"
|
|
128
|
+
>
|
|
129
|
+
{{ errorOf(slug) }}
|
|
130
|
+
</p>
|
|
131
|
+
}
|
|
132
|
+
</div>
|
|
133
|
+
} @if (!allRequiredFilled()) {
|
|
134
|
+
<p class="bas-m-0 bas-text-center bas-text-xs bas-text-amber-600">
|
|
135
|
+
{{ fillRequiredTxtResolved() }}
|
|
136
|
+
</p>
|
|
124
137
|
}
|
|
125
138
|
</div>
|
|
126
139
|
`,
|
|
@@ -129,13 +142,26 @@ export class SolicitedDataModalComponent {
|
|
|
129
142
|
requiredParams = input<string[]>([]);
|
|
130
143
|
optionalParams = input<string[]>([]);
|
|
131
144
|
userParamValues = input<Record<string, string>>({});
|
|
132
|
-
requiredLabelTxt = input(
|
|
133
|
-
optionalLabelTxt = input(
|
|
134
|
-
fillRequiredTxt = input(
|
|
145
|
+
requiredLabelTxt = input<string>();
|
|
146
|
+
optionalLabelTxt = input<string>();
|
|
147
|
+
fillRequiredTxt = input<string>();
|
|
135
148
|
|
|
136
149
|
paramChange = output<ParamChange>();
|
|
137
150
|
validationChange = output<boolean>();
|
|
138
151
|
|
|
152
|
+
private readonly locale = injectStore($locale);
|
|
153
|
+
protected t = (key: TextKey): string => translate(key, this.locale());
|
|
154
|
+
|
|
155
|
+
protected readonly requiredLabelTxtResolved = computed(
|
|
156
|
+
() => this.requiredLabelTxt() ?? this.t('required'),
|
|
157
|
+
);
|
|
158
|
+
protected readonly optionalLabelTxtResolved = computed(
|
|
159
|
+
() => this.optionalLabelTxt() ?? this.t('optional'),
|
|
160
|
+
);
|
|
161
|
+
protected readonly fillRequiredTxtResolved = computed(
|
|
162
|
+
() => this.fillRequiredTxt() ?? this.t('fillRequiredFields'),
|
|
163
|
+
);
|
|
164
|
+
|
|
139
165
|
private readonly touched = signal<Record<string, boolean>>({});
|
|
140
166
|
|
|
141
167
|
protected readonly allRequiredFilled = computed(() =>
|
|
@@ -152,7 +178,8 @@ export class SolicitedDataModalComponent {
|
|
|
152
178
|
PARAM_DISPLAY_ORDER.filter(
|
|
153
179
|
(slug) =>
|
|
154
180
|
slug !== 'modules-kyc' &&
|
|
155
|
-
(this.requiredParams().includes(slug) ||
|
|
181
|
+
(this.requiredParams().includes(slug) ||
|
|
182
|
+
this.optionalParams().includes(slug)),
|
|
156
183
|
),
|
|
157
184
|
);
|
|
158
185
|
|
|
@@ -163,14 +190,17 @@ export class SolicitedDataModalComponent {
|
|
|
163
190
|
}
|
|
164
191
|
|
|
165
192
|
isRequired = (slug: string): boolean => this.requiredParams().includes(slug);
|
|
166
|
-
label = (slug: string): string =>
|
|
193
|
+
label = (slug: string): string => {
|
|
194
|
+
const key = PARAM_LABEL_KEYS[slug];
|
|
195
|
+
return key ? this.t(key) : slug;
|
|
196
|
+
};
|
|
167
197
|
inputType = (slug: string): string => PARAM_INPUT_TYPE[slug] || 'text';
|
|
168
198
|
valueOf = (slug: string): string => this.userParamValues()[slug] || '';
|
|
169
199
|
|
|
170
200
|
private getError(slug: string, value: string): string | null {
|
|
171
201
|
if (!value) return null;
|
|
172
202
|
const validate = PARAM_VALIDATORS[slug];
|
|
173
|
-
if (validate && !validate(value)) return
|
|
203
|
+
if (validate && !validate(value)) return this.t(PARAM_ERROR_KEYS[slug]);
|
|
174
204
|
return null;
|
|
175
205
|
}
|
|
176
206
|
|
|
@@ -182,6 +212,9 @@ export class SolicitedDataModalComponent {
|
|
|
182
212
|
}
|
|
183
213
|
|
|
184
214
|
onInput(slug: string, event: Event): void {
|
|
185
|
-
this.paramChange.emit({
|
|
215
|
+
this.paramChange.emit({
|
|
216
|
+
slug,
|
|
217
|
+
value: (event.target as HTMLInputElement).value,
|
|
218
|
+
});
|
|
186
219
|
}
|
|
187
220
|
}
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import { Component } from '@angular/core';
|
|
3
3
|
import { render, screen, fireEvent } from '@testing-library/angular';
|
|
4
|
+
import { setLocale } from '@block-auth.io/core';
|
|
4
5
|
import { TxnErrorComponent } from './txn-error.component';
|
|
5
6
|
import { provideBlockAuthClientForTest } from '../../../testing';
|
|
6
7
|
|
|
7
8
|
vi.mock('@wagmi/core', () => ({
|
|
8
9
|
createConfig: vi.fn(() => ({ connectors: [] })),
|
|
9
|
-
connect: vi.fn(),
|
|
10
|
-
|
|
10
|
+
connect: vi.fn(),
|
|
11
|
+
disconnect: vi.fn(),
|
|
12
|
+
getAccount: vi.fn(() => ({})),
|
|
13
|
+
signMessage: vi.fn(),
|
|
14
|
+
watchAccount: vi.fn(() => () => undefined),
|
|
15
|
+
http: vi.fn(),
|
|
11
16
|
}));
|
|
12
17
|
vi.mock('@wagmi/connectors', () => ({
|
|
13
|
-
injected: vi.fn(() => ({ id: 'injected' })),
|
|
18
|
+
injected: vi.fn(() => ({ id: 'injected' })),
|
|
19
|
+
walletConnect: vi.fn(() => ({ id: 'walletConnect' })),
|
|
14
20
|
}));
|
|
15
21
|
|
|
16
22
|
// Port de packages/react/src/components/txns/internals/TxnError.test.tsx +
|
|
@@ -20,6 +26,8 @@ describe('TxnError', () => {
|
|
|
20
26
|
return { txns: { report } } as any;
|
|
21
27
|
}
|
|
22
28
|
|
|
29
|
+
afterEach(() => setLocale('en'));
|
|
30
|
+
|
|
23
31
|
it('renderiza el panel de error de txn', async () => {
|
|
24
32
|
await render(TxnErrorComponent, {
|
|
25
33
|
inputs: { errorMessageTxt: 'No transactions found' },
|
|
@@ -29,10 +37,22 @@ describe('TxnError', () => {
|
|
|
29
37
|
expect(screen.getByText('REPORT')).toBeDefined();
|
|
30
38
|
});
|
|
31
39
|
|
|
40
|
+
it('respeta el locale activo cuando no se pasan textos custom', async () => {
|
|
41
|
+
setLocale('es');
|
|
42
|
+
await render(TxnErrorComponent, {
|
|
43
|
+
inputs: { errorMessageTxt: 'No transactions found' },
|
|
44
|
+
providers: [provideBlockAuthClientForTest(makeClient())],
|
|
45
|
+
});
|
|
46
|
+
expect(screen.getByText('REPORTAR')).toBeDefined();
|
|
47
|
+
});
|
|
48
|
+
|
|
32
49
|
it('REPORT llama a client.txns.report(slug)', async () => {
|
|
33
50
|
const report = vi.fn();
|
|
34
51
|
await render(TxnErrorComponent, {
|
|
35
|
-
inputs: {
|
|
52
|
+
inputs: {
|
|
53
|
+
errorMessageTxt: 'No transactions found',
|
|
54
|
+
errorMessageSlug: 'no_transactions_found',
|
|
55
|
+
},
|
|
36
56
|
providers: [provideBlockAuthClientForTest(makeClient(report))],
|
|
37
57
|
});
|
|
38
58
|
await fireEvent.click(screen.getByText('REPORT'));
|
|
@@ -52,7 +72,10 @@ describe('TxnError', () => {
|
|
|
52
72
|
@Component({
|
|
53
73
|
standalone: true,
|
|
54
74
|
imports: [TxnErrorComponent],
|
|
55
|
-
template: `<ba-txn-error
|
|
75
|
+
template: `<ba-txn-error
|
|
76
|
+
errorMessageTxt="No transactions found"
|
|
77
|
+
[closeTxns]="c"
|
|
78
|
+
/>`,
|
|
56
79
|
})
|
|
57
80
|
class Host {
|
|
58
81
|
c = closeTxns;
|
|
@@ -78,7 +101,11 @@ describe('TxnError', () => {
|
|
|
78
101
|
@Component({
|
|
79
102
|
standalone: true,
|
|
80
103
|
imports: [TxnErrorComponent],
|
|
81
|
-
template: `<ba-txn-error
|
|
104
|
+
template: `<ba-txn-error
|
|
105
|
+
errorMessageTxt="No transactions found"
|
|
106
|
+
[closeTxns]="c"
|
|
107
|
+
[isCountdown]="false"
|
|
108
|
+
/>`,
|
|
82
109
|
})
|
|
83
110
|
class Host {
|
|
84
111
|
c = closeTxns;
|
|
@@ -2,10 +2,13 @@ import {
|
|
|
2
2
|
ChangeDetectionStrategy,
|
|
3
3
|
Component,
|
|
4
4
|
DestroyRef,
|
|
5
|
+
computed,
|
|
5
6
|
inject,
|
|
6
7
|
input,
|
|
7
8
|
signal,
|
|
8
9
|
} from '@angular/core';
|
|
10
|
+
import { $locale, t as translate, type TextKey } from '@block-auth.io/core';
|
|
11
|
+
import { injectStore } from '../../../inject-store';
|
|
9
12
|
import { injectBlockAuthClient } from '../../../provide-block-auth';
|
|
10
13
|
|
|
11
14
|
// `closeTxns` se mantiene como INPUT DE FUNCIÓN (no output): react lo invoca
|
|
@@ -31,40 +34,42 @@ import { injectBlockAuthClient } from '../../../provide-block-auth';
|
|
|
31
34
|
>
|
|
32
35
|
<p class="bas-m-0">{{ errorMessageTxt() }}</p>
|
|
33
36
|
</div>
|
|
34
|
-
<div
|
|
37
|
+
<div
|
|
38
|
+
class="bas-box-border bas-flex bas-w-full bas-flex-col bas-items-center bas-justify-center bas-gap-1 bas-px-1"
|
|
39
|
+
>
|
|
35
40
|
<div class="bas-h-16 bas-w-full">
|
|
36
41
|
@if (!reportSent()) {
|
|
37
|
-
|
|
42
|
+
<p class="bas-m-0">{{ helpUsTxtResolved() }}</p>
|
|
38
43
|
} @else {
|
|
39
|
-
|
|
44
|
+
<p class="bas-m-0">{{ thanksForReportTxtResolved() }}</p>
|
|
40
45
|
}
|
|
41
46
|
</div>
|
|
42
47
|
@if (!reportSent()) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
id="btn-txn-report-error"
|
|
51
|
+
[disabled]="sendingReport()"
|
|
52
|
+
class="bal-ba-hover-ring-red-500 bal-ba-focus-ring-red-500 bas-mt-2 bas-w-[220px] bas-justify-center bas-bg-red-500 hover:bas-bg-red-600 bas-px-4 bas-text-center bas-text-xl bas-font-medium bas-text-[var(--bal-primary-10)]"
|
|
53
|
+
(click)="onReportErrorClicked()"
|
|
54
|
+
>
|
|
55
|
+
{{ sendingReport() ? '…' : reportTxtResolved() }}
|
|
56
|
+
</button>
|
|
52
57
|
} @else {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
id="btn-txn-error-report"
|
|
61
|
+
class="bal-ba-hover-ring-primary-300 bal-ba-focus-ring-primary-300 bas-mt-2 bas-w-[220px] bas-justify-center bas-rounded-md bas-border-2 bas-border-solid bas-border-[var(--bal-primary-300)] bas-bg-transparent bas-px-4 bas-text-center bas-text-xl bas-font-medium bas-text-[var(--bal-primary-700)] group-hover:bas-text-[var(--bal-primary-900)]"
|
|
62
|
+
(click)="close()"
|
|
63
|
+
>
|
|
64
|
+
{{ textCloseTxtResolved() }}
|
|
65
|
+
</button>
|
|
61
66
|
}
|
|
62
67
|
</div>
|
|
63
68
|
@if (reportSent() && isCountdown()) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
<div class="bas-flex bas-flex-row bas-items-end bas-gap-2">
|
|
70
|
+
<p class="bas-m-0">{{ modalWillCloseInTxtResolved() }}</p>
|
|
71
|
+
<p class="bas-m-0">{{ countDown() }}</p>
|
|
72
|
+
</div>
|
|
68
73
|
}
|
|
69
74
|
</div>
|
|
70
75
|
`,
|
|
@@ -73,14 +78,32 @@ export class TxnErrorComponent {
|
|
|
73
78
|
errorMessageSlug = input('unknown');
|
|
74
79
|
errorMessageTxt = input('unknown');
|
|
75
80
|
closeTxns = input<() => void>(() => {});
|
|
76
|
-
helpUsTxt = input(
|
|
77
|
-
thanksForReportTxt = input(
|
|
78
|
-
reportTxt = input(
|
|
79
|
-
textCloseTxt = input(
|
|
80
|
-
modalWillCloseInTxt = input(
|
|
81
|
+
helpUsTxt = input<string>();
|
|
82
|
+
thanksForReportTxt = input<string>();
|
|
83
|
+
reportTxt = input<string>();
|
|
84
|
+
textCloseTxt = input<string>();
|
|
85
|
+
modalWillCloseInTxt = input<string>();
|
|
81
86
|
isCountdown = input(true);
|
|
82
87
|
|
|
83
88
|
private readonly client = injectBlockAuthClient();
|
|
89
|
+
private readonly locale = injectStore($locale);
|
|
90
|
+
protected t = (key: TextKey): string => translate(key, this.locale());
|
|
91
|
+
|
|
92
|
+
protected readonly helpUsTxtResolved = computed(
|
|
93
|
+
() => this.helpUsTxt() ?? this.t('helpUsReport'),
|
|
94
|
+
);
|
|
95
|
+
protected readonly thanksForReportTxtResolved = computed(
|
|
96
|
+
() => this.thanksForReportTxt() ?? this.t('thanksForReport'),
|
|
97
|
+
);
|
|
98
|
+
protected readonly reportTxtResolved = computed(
|
|
99
|
+
() => this.reportTxt() ?? this.t('report'),
|
|
100
|
+
);
|
|
101
|
+
protected readonly textCloseTxtResolved = computed(
|
|
102
|
+
() => this.textCloseTxt() ?? this.t('close'),
|
|
103
|
+
);
|
|
104
|
+
protected readonly modalWillCloseInTxtResolved = computed(
|
|
105
|
+
() => this.modalWillCloseInTxt() ?? this.t('modalWillCloseIn'),
|
|
106
|
+
);
|
|
84
107
|
|
|
85
108
|
protected readonly sendingReport = signal(false);
|
|
86
109
|
protected readonly reportSent = signal(false);
|