Skip to content

Commit fc8e159

Browse files
committed
Merge branch 'feat/supermind-composer-validation-f5777' into 'master'
Feat/supermind composer validation f5777 See merge request minds/front!2068
2 parents 548c391 + 925a8a7 commit fc8e159

11 files changed

+1590
-4494
lines changed

package-lock.json

+1,306-4,449
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/common/components/forms/autocomplete-user-input/autocomplete-user-input.component.html

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
(focus)="onInputFocus($event)"
66
(blur)="onInputBlur($event)"
77
[placeholder]="placeholder"
8+
[class.ng-invalid]="isInvalid"
89
#input
910
/>
1011
<ul

src/app/common/components/forms/autocomplete-user-input/autocomplete-user-input.component.ng.scss

+6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66

77
input {
88
width: 100%;
9+
10+
&.ng-dirty.ng-invalid {
11+
@include m-theme() {
12+
border-color: themed($m-alert) !important;
13+
}
14+
}
915
}
1016

1117
.m-formInputAutocompleteUserInput__popout {

src/app/common/components/forms/autocomplete-user-input/autocomplete-user-input.component.ts

+13-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
ElementRef,
44
forwardRef,
55
Input,
6+
Self,
67
ViewChild,
78
} from '@angular/core';
89
import {
@@ -25,7 +26,7 @@ import {
2526
import { ApiService } from '../../../api/api.service';
2627
import { ConfigsService } from '../../../services/configs.service';
2728
import { MindsUser } from '../../../../interfaces/entities';
28-
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
29+
import { ControlValueAccessor, NgControl } from '@angular/forms';
2930
import { FastFadeAnimation } from '../../../../animations';
3031

3132
/**
@@ -34,7 +35,6 @@ import { FastFadeAnimation } from '../../../../animations';
3435
export type UserSearchResponse = { status: string; entities: MindsUser[] };
3536

3637
export const FORM_INPUT_AUTOCOMPLETE_USER_INPUT_VALUE_ACCESSOR: any = {
37-
provide: NG_VALUE_ACCESSOR,
3838
useExisting: forwardRef(() => AutocompleteUserInputComponent),
3939
multi: true,
4040
};
@@ -48,7 +48,7 @@ export const FORM_INPUT_AUTOCOMPLETE_USER_INPUT_VALUE_ACCESSOR: any = {
4848
selector: 'm-formInput__autocompleteUserInput',
4949
templateUrl: './autocomplete-user-input.component.html',
5050
styleUrls: ['./autocomplete-user-input.component.ng.scss'],
51-
providers: [FORM_INPUT_AUTOCOMPLETE_USER_INPUT_VALUE_ACCESSOR],
51+
// providers: [FORM_INPUT_AUTOCOMPLETE_USER_INPUT_VALUE_ACCESSOR],
5252
animations: [FastFadeAnimation],
5353
})
5454
export class AutocompleteUserInputComponent implements ControlValueAccessor {
@@ -141,8 +141,13 @@ export class AutocompleteUserInputComponent implements ControlValueAccessor {
141141
*/
142142
readonly cdnUrl: string;
143143

144-
constructor(private api: ApiService, configs: ConfigsService) {
144+
constructor(
145+
@Self() private control: NgControl,
146+
private api: ApiService,
147+
configs: ConfigsService
148+
) {
145149
this.cdnUrl = configs.get('cdn_url');
150+
this.control.valueAccessor = this;
146151
}
147152

148153
ngOnInit() {
@@ -158,6 +163,10 @@ export class AutocompleteUserInputComponent implements ControlValueAccessor {
158163
this.usernameSubscription.unsubscribe();
159164
}
160165

166+
get isInvalid() {
167+
return this.control.invalid;
168+
}
169+
161170
/**
162171
* Emits the username, will trigger rxjs flow
163172
* @param username

src/app/common/services/configs.service.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Client } from '../api/client.service';
22
import { BehaviorSubject } from 'rxjs';
3-
import { Injectable, Inject } from '@angular/core';
3+
import { Inject, Injectable } from '@angular/core';
44
import { RedirectService } from './redirect.service';
55
import { Location } from '@angular/common';
66

@@ -28,8 +28,8 @@ export class ConfigsService {
2828
}
2929
}
3030

31-
get(key) {
32-
return this.configs[key] || null;
31+
get<T = any>(key): T | null {
32+
return (this.configs[key] as T) || null;
3333
}
3434

3535
set(key, value): void {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { Injectable } from '@angular/core';
2+
import { ApiService } from '../api/api.service';
3+
import { Observable, of } from 'rxjs';
4+
import { catchError } from 'rxjs/operators';
5+
6+
export class EntityResolverServiceOptions {
7+
get ref(): string {
8+
return this._ref;
9+
}
10+
11+
set ref(value: string) {
12+
this._ref = value;
13+
}
14+
15+
get refType(): 'guid' | 'username' {
16+
return this._refType;
17+
}
18+
19+
set refType(value: 'guid' | 'username') {
20+
this._refType = value;
21+
}
22+
23+
private _refType: 'guid' | 'username';
24+
25+
private _ref: string;
26+
}
27+
28+
@Injectable({ providedIn: 'root' })
29+
export class EntityResolverService {
30+
constructor(private apiService: ApiService) {}
31+
32+
public get$<T>(opt: EntityResolverServiceOptions): Observable<T | null> {
33+
return this.apiService
34+
.get<T>(`api/v3/entities/${opt.ref}`)
35+
.pipe(catchError(e => of(null)));
36+
}
37+
}

src/app/interfaces/entities.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/**
22
* Activity Object
33
*/
4+
import { SupermindSettings } from '../modules/settings-v2/payments/supermind/supermind.types';
45
import { WireRewardsStruc } from '../modules/wire/interfaces/wire.interfaces';
56

67
export type BitBoolean = 0 | 1;
@@ -136,6 +137,7 @@ export interface MindsUser {
136137
seed?: boolean;
137138
require_login?: boolean;
138139
email_confirmed?: boolean;
140+
supermind_settings?: SupermindSettings;
139141
}
140142

141143
export interface MindsGroup {

src/app/modules/composer/components/popup/supermind/supermind.component.html

+18-8
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,22 @@ <h3 class="m-composerPopup__title" i18n="@@COMMON__SUPERMIND">Supermind</h3>
3636
<label> Target Channel</label>
3737
</div>
3838
<div class="m-form__row--input">
39-
<!-- <input
40-
type="text"
41-
id="username"
42-
formControlName="username"
43-
placeholder="@username"
44-
/> -->
4539
<m-formInput__autocompleteUserInput
4640
id="username"
4741
formControlName="username"
4842
placeholder="@username"
4943
></m-formInput__autocompleteUserInput>
5044
</div>
45+
<div
46+
class="m-form__row--validation"
47+
*ngIf="
48+
!inProgress && formGroup.controls.username.errors?.merchantInvalid
49+
"
50+
>
51+
<p i18n="@@MINDS_SUPERMIND_COMPOSER__MERCHANT_INVALID">
52+
@{{ formGroup.controls.username.value }} cannot receive cash offers
53+
</p>
54+
</div>
5155
</div>
5256

5357
<!------------------------>
@@ -64,7 +68,12 @@ <h3 class="m-composerPopup__title" i18n="@@COMMON__SUPERMIND">Supermind</h3>
6468
>
6569
</div>
6670
<div class="m-form__row--input">
67-
<input type="number" id="offer_usd" formControlName="offerUsd" />
71+
<input
72+
type="number"
73+
[pattern]="'([0-9]+\\.[0-9]{1,2}|[0-9]+)'"
74+
id="offer_usd"
75+
formControlName="offerUsd"
76+
/>
6877
</div>
6978
</div>
7079

@@ -100,6 +109,7 @@ <h3 class="m-composerPopup__title" i18n="@@COMMON__SUPERMIND">Supermind</h3>
100109
<div class="m-form__row--input">
101110
<input
102111
type="number"
112+
[pattern]="'([0-9]+\\.[0-9]{1,2}|[0-9]+)'"
103113
id="offer_tokens"
104114
formControlName="offerTokens"
105115
/>
@@ -218,7 +228,7 @@ <h3 class="m-composerPopup__title" i18n="@@COMMON__SUPERMIND">Supermind</h3>
218228
i18n="@@COMPOSER_POPUP__SAVE_TAGS"
219229
size="small"
220230
color="blue"
221-
[disabled]="!formGroup.valid"
231+
[disabled]="inProgress || !formGroup.valid"
222232
>
223233
Save
224234
</m-button>

src/app/modules/composer/components/popup/supermind/supermind.component.spec.ts

+35-12
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
11
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
22
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
33
import { By } from '@angular/platform-browser';
4-
import { BehaviorSubject } from 'rxjs';
54
import { ApiService } from '../../../../../common/api/api.service';
65
import { CommonModule } from '../../../../../common/common.module';
76
import { ButtonComponent } from '../../../../../common/components/button/button.component';
8-
import { AutocompleteUserInputComponent } from '../../../../../common/components/forms/autocomplete-user-input/autocomplete-user-input.component';
97
import { ConfigsService } from '../../../../../common/services/configs.service';
10-
import { Client } from '../../../../../services/api';
11-
import { Session } from '../../../../../services/session';
128
import { MockComponent, MockService } from '../../../../../utils/mock';
13-
import { PaymentsModule } from '../../../../payments/payments.module';
14-
import {
15-
ComposerService,
16-
ComposerSize,
17-
} from '../../../services/composer.service';
9+
import { ComposerService } from '../../../services/composer.service';
1810
import { PopupService } from '../popup.service';
1911
import { ComposerSupermindComponent } from '../supermind/supermind.component';
12+
import { EntityResolverService } from '../../../../../common/services/entity-resolver.service';
13+
import { of } from 'rxjs';
2014

2115
describe('Composer Supermind Popup', () => {
2216
let comp: ComposerSupermindComponent;
@@ -49,6 +43,10 @@ describe('Composer Supermind Popup', () => {
4943
present: { toPromise: () => {} },
5044
});
5145

46+
const apiMock = new (function() {
47+
this.get = jasmine.createSpy('get');
48+
})();
49+
5250
beforeEach(
5351
waitForAsync(() => {
5452
TestBed.configureTestingModule({
@@ -71,7 +69,7 @@ describe('Composer Supermind Popup', () => {
7169
},
7270
{
7371
provide: ApiService,
74-
useValue: MockService(ApiService),
72+
useValue: apiMock,
7573
},
7674
// {
7775
// provide: Client,
@@ -84,7 +82,11 @@ describe('Composer Supermind Popup', () => {
8482
// {
8583
// provide: Session,
8684
// useValue: MockService(Session),
87-
// }
85+
// },
86+
{
87+
provide: EntityResolverService,
88+
useValue: MockService(EntityResolverService),
89+
},
8890
],
8991
}).compileComponents();
9092
})
@@ -93,6 +95,27 @@ describe('Composer Supermind Popup', () => {
9395
beforeEach(done => {
9496
fixture = TestBed.createComponent(ComposerSupermindComponent);
9597
comp = fixture.componentInstance;
98+
99+
(comp as any).mindsConfig.get.and.returnValue({
100+
min_thresholds: {
101+
min_cash: 10,
102+
min_offchain_tokens: 1,
103+
},
104+
});
105+
106+
(comp as any).entityResolverService.get$.and.returnValue(
107+
of({
108+
supermind_settings: {
109+
min_cash: 10,
110+
min_offchain_tokens: 1,
111+
},
112+
merchant: {},
113+
})
114+
);
115+
116+
apiMock.get.calls.reset();
117+
apiMock.get.and.returnValue([]);
118+
96119
fixture.detectChanges();
97120

98121
if (fixture.isStable()) {
@@ -128,13 +151,13 @@ describe('Composer Supermind Popup', () => {
128151
comp.formGroup.controls.termsAccepted.setValue(true);
129152
comp.formGroup.controls.username.setValue('minds');
130153
fixture.detectChanges();
131-
132154
expect(getSaveBtn().disabled).toBeFalse();
133155
});
134156

135157
it('should update composer supermindRequest$ service on save', () => {
136158
comp.formGroup.controls.termsAccepted.setValue(true);
137159
comp.formGroup.controls.username.setValue('minds');
160+
// comp.formGroup.controls.username.markAsTouched({ onlySelf: true });
138161
fixture.detectChanges();
139162

140163
getSaveBtn().onAction.next(new MouseEvent('click'));

0 commit comments

Comments
 (0)