@nauticana/sail 0.7.0 → 0.8.1
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/README.md +419 -15
- package/package.json +2 -2
- package/src/component/abstract/base_async.ts +8 -2
- package/src/component/abstract/base_table.ts +108 -49
- package/src/component/abstract/base_view.ts +32 -45
- package/src/component/billing/checkout_button.html +1 -1
- package/src/component/billing/checkout_button.ts +1 -1
- package/src/component/billing/payment_methods.ts +1 -1
- package/src/component/billing/plan_selector.html +1 -1
- package/src/component/billing/plan_selector.ts +1 -1
- package/src/component/form/form_field.html +1 -1
- package/src/component/form/form_field.ts +8 -7
- package/src/component/form/form_record.html +2 -2
- package/src/component/form/form_record.ts +1 -1
- package/src/component/form/form_table.html +4 -4
- package/src/component/form/form_table.ts +2 -2
- package/src/component/login/account_deletion.html +1 -1
- package/src/component/login/account_deletion.ts +5 -2
- package/src/component/login/chpass_component.html +1 -1
- package/src/component/login/chpass_component.ts +11 -16
- package/src/component/login/confirm_chpass_component.html +1 -1
- package/src/component/login/confirm_chpass_component.ts +9 -15
- package/src/component/login/confirm_register_component.html +1 -1
- package/src/component/login/confirm_register_component.ts +9 -15
- package/src/component/login/consent_gate.ts +1 -1
- package/src/component/login/login_component.html +2 -2
- package/src/component/login/login_component.ts +1 -1
- package/src/component/login/otp_input.html +3 -3
- package/src/component/login/otp_input.ts +1 -1
- package/src/component/login/register_component.html +1 -1
- package/src/component/login/register_component.ts +1 -1
- package/src/component/login/social_login.ts +1 -1
- package/src/component/navigation/navigation.html +3 -3
- package/src/component/navigation/navigation.ts +10 -11
- package/src/component/payment/user_payment_methods.html +46 -0
- package/src/component/payment/user_payment_methods.ts +10 -58
- package/src/component/payout/bank_info_form.html +59 -0
- package/src/component/payout/bank_info_form.ts +21 -77
- package/src/component/payout/provider_onboarding.html +70 -0
- package/src/component/payout/provider_onboarding.ts +9 -80
- package/src/component/security/trusted_devices.html +4 -4
- package/src/component/security/trusted_devices.ts +1 -1
- package/src/component/security/twofactor_setup.html +4 -4
- package/src/component/security/twofactor_setup.ts +18 -26
- package/src/component/security/twofactor_verify.html +1 -1
- package/src/component/security/twofactor_verify.ts +10 -12
- package/src/component/table/table_detail.html +12 -12
- package/src/component/table/table_detail.ts +28 -33
- package/src/component/table/table_edit.html +20 -10
- package/src/component/table/table_edit.ts +29 -18
- package/src/component/table/table_list.html +5 -5
- package/src/component/table/table_list.ts +24 -48
- package/src/component/table/table_lookup.html +4 -4
- package/src/component/table/table_lookup.ts +11 -12
- package/src/component/table/table_report.html +1 -1
- package/src/component/table/table_report.ts +11 -14
- package/src/component/table/table_search.html +5 -5
- package/src/component/table/table_search.ts +31 -42
- package/src/index.ts +2 -0
- package/src/model/appdata.ts +2 -2
- package/src/model/common.ts +39 -1
- package/src/service/auth.service.ts +26 -26
- package/src/service/base_rest.service.ts +20 -0
- package/src/service/billing.service.ts +3 -9
- package/src/service/payout.service.ts +3 -9
- package/src/service/rest_service.ts +5 -5
- package/src/service/user_payment_method.service.ts +4 -8
- package/src/util/text.ts +19 -0
|
@@ -7,9 +7,10 @@ import { MatFormFieldModule } from '@angular/material/form-field';
|
|
|
7
7
|
import { MatInputModule } from '@angular/material/input';
|
|
8
8
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
9
9
|
import { SAIL_GUI_CONFIG, SailGuiConfig, DEFAULT_CONFIG } from '../../config';
|
|
10
|
+
import { BaseAsync } from '../abstract/base_async';
|
|
10
11
|
|
|
11
12
|
@Component({
|
|
12
|
-
selector: '
|
|
13
|
+
selector: 'sail-chpass',
|
|
13
14
|
templateUrl: './chpass_component.html',
|
|
14
15
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
15
16
|
encapsulation: ViewEncapsulation.None,
|
|
@@ -22,15 +23,13 @@ import { SAIL_GUI_CONFIG, SailGuiConfig, DEFAULT_CONFIG } from '../../config';
|
|
|
22
23
|
RouterLink,
|
|
23
24
|
],
|
|
24
25
|
})
|
|
25
|
-
export class ChpassComponent {
|
|
26
|
+
export class ChpassComponent extends BaseAsync {
|
|
26
27
|
private auth = inject(BaseAuthService);
|
|
27
28
|
private fb = inject(FormBuilder);
|
|
28
29
|
private router = inject(Router);
|
|
29
30
|
protected readonly guiConfig: SailGuiConfig = inject(SAIL_GUI_CONFIG, {optional: true}) ?? DEFAULT_CONFIG;
|
|
30
31
|
|
|
31
32
|
forgotPassword = signal(false);
|
|
32
|
-
errorMessage = signal('');
|
|
33
|
-
successMessage = signal('');
|
|
34
33
|
|
|
35
34
|
chpassForm = this.fb.group({
|
|
36
35
|
username: ['', Validators.required],
|
|
@@ -62,9 +61,6 @@ export class ChpassComponent {
|
|
|
62
61
|
}
|
|
63
62
|
|
|
64
63
|
changePassword(): void {
|
|
65
|
-
this.errorMessage.set('');
|
|
66
|
-
this.successMessage.set('');
|
|
67
|
-
|
|
68
64
|
if (this.chpassForm.invalid) return;
|
|
69
65
|
|
|
70
66
|
const { username, old_password, new_password, confirm_password } = this.chpassForm.value;
|
|
@@ -74,23 +70,22 @@ export class ChpassComponent {
|
|
|
74
70
|
return;
|
|
75
71
|
}
|
|
76
72
|
|
|
77
|
-
const
|
|
73
|
+
const isForgot = this.forgotPassword();
|
|
74
|
+
const request = isForgot
|
|
78
75
|
? this.auth.forgotPassword(username!)
|
|
79
76
|
: this.auth.chpass(username!, new_password!, old_password!);
|
|
80
77
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
this.run(
|
|
79
|
+
request,
|
|
80
|
+
() => {
|
|
81
|
+
if (isForgot) {
|
|
84
82
|
this.successMessage.set('A confirmation code has been sent to your email.');
|
|
85
83
|
this.router.navigate(['/confirm/password'], { queryParams: { username } });
|
|
86
84
|
} else {
|
|
87
85
|
this.successMessage.set('Password changed successfully.');
|
|
88
86
|
}
|
|
89
87
|
},
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
this.errorMessage.set(msg || 'Password change failed. Please try again.');
|
|
93
|
-
},
|
|
94
|
-
});
|
|
88
|
+
'Password change failed. Please try again.',
|
|
89
|
+
);
|
|
95
90
|
}
|
|
96
91
|
}
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
<div class="auth-success" role="status">{{ successMessage() }}</div>
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
<button
|
|
39
|
+
<button matButton="elevated" class="primary" type="submit" [disabled]="!confirmForm.valid">
|
|
40
40
|
Confirm Password Change
|
|
41
41
|
</button>
|
|
42
42
|
</form>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChangeDetectionStrategy, Component, inject,
|
|
1
|
+
import { ChangeDetectionStrategy, Component, inject, OnInit, ViewEncapsulation } from '@angular/core';
|
|
2
2
|
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
3
3
|
import { BaseAuthService } from '../../service/auth.service';
|
|
4
4
|
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
|
@@ -6,9 +6,10 @@ import { MatButtonModule } from '@angular/material/button';
|
|
|
6
6
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
7
7
|
import { MatInputModule } from '@angular/material/input';
|
|
8
8
|
import { SAIL_GUI_CONFIG, SailGuiConfig, DEFAULT_CONFIG } from '../../config';
|
|
9
|
+
import { BaseAsync } from '../abstract/base_async';
|
|
9
10
|
|
|
10
11
|
@Component({
|
|
11
|
-
selector: '
|
|
12
|
+
selector: 'sail-confirm-chpass',
|
|
12
13
|
templateUrl: './confirm_chpass_component.html',
|
|
13
14
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
14
15
|
encapsulation: ViewEncapsulation.None,
|
|
@@ -20,16 +21,13 @@ import { SAIL_GUI_CONFIG, SailGuiConfig, DEFAULT_CONFIG } from '../../config';
|
|
|
20
21
|
RouterLink,
|
|
21
22
|
],
|
|
22
23
|
})
|
|
23
|
-
export class ConfirmChpassComponent implements OnInit {
|
|
24
|
+
export class ConfirmChpassComponent extends BaseAsync implements OnInit {
|
|
24
25
|
private auth = inject(BaseAuthService);
|
|
25
26
|
private fb = inject(FormBuilder);
|
|
26
27
|
private router = inject(Router);
|
|
27
28
|
private route = inject(ActivatedRoute);
|
|
28
29
|
protected readonly guiConfig: SailGuiConfig = inject(SAIL_GUI_CONFIG, {optional: true}) ?? DEFAULT_CONFIG;
|
|
29
30
|
|
|
30
|
-
errorMessage = signal('');
|
|
31
|
-
successMessage = signal('');
|
|
32
|
-
|
|
33
31
|
confirmForm = this.fb.group({
|
|
34
32
|
username: ['', Validators.required],
|
|
35
33
|
code: ['', Validators.required],
|
|
@@ -45,9 +43,6 @@ export class ConfirmChpassComponent implements OnInit {
|
|
|
45
43
|
}
|
|
46
44
|
|
|
47
45
|
confirm(): void {
|
|
48
|
-
this.errorMessage.set('');
|
|
49
|
-
this.successMessage.set('');
|
|
50
|
-
|
|
51
46
|
if (this.confirmForm.invalid) return;
|
|
52
47
|
|
|
53
48
|
const { username, code, new_password, confirm_password } = this.confirmForm.value;
|
|
@@ -57,14 +52,13 @@ export class ConfirmChpassComponent implements OnInit {
|
|
|
57
52
|
return;
|
|
58
53
|
}
|
|
59
54
|
|
|
60
|
-
this.
|
|
61
|
-
|
|
55
|
+
this.run(
|
|
56
|
+
this.auth.confirmChpass(username!, code!, new_password!),
|
|
57
|
+
() => {
|
|
62
58
|
this.successMessage.set('Password changed successfully. Redirecting to login...');
|
|
63
59
|
this.router.navigate(['/login/local']);
|
|
64
60
|
},
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
},
|
|
68
|
-
});
|
|
61
|
+
'Confirmation failed. Please try again.',
|
|
62
|
+
);
|
|
69
63
|
}
|
|
70
64
|
}
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
<div class="auth-success" role="status">{{ successMessage() }}</div>
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
<button
|
|
29
|
+
<button matButton="elevated" class="primary" type="submit" [disabled]="!confirmForm.valid">
|
|
30
30
|
Confirm
|
|
31
31
|
</button>
|
|
32
32
|
</form>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChangeDetectionStrategy, Component, inject,
|
|
1
|
+
import { ChangeDetectionStrategy, Component, inject, OnInit, ViewEncapsulation } from '@angular/core';
|
|
2
2
|
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
3
3
|
import { BaseAuthService } from '../../service/auth.service';
|
|
4
4
|
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
|
@@ -6,9 +6,10 @@ import { MatButtonModule } from '@angular/material/button';
|
|
|
6
6
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
7
7
|
import { MatInputModule } from '@angular/material/input';
|
|
8
8
|
import { SAIL_GUI_CONFIG, SailGuiConfig, DEFAULT_CONFIG } from '../../config';
|
|
9
|
+
import { BaseAsync } from '../abstract/base_async';
|
|
9
10
|
|
|
10
11
|
@Component({
|
|
11
|
-
selector: '
|
|
12
|
+
selector: 'sail-confirm-register',
|
|
12
13
|
templateUrl: './confirm_register_component.html',
|
|
13
14
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
14
15
|
encapsulation: ViewEncapsulation.None,
|
|
@@ -20,16 +21,13 @@ import { SAIL_GUI_CONFIG, SailGuiConfig, DEFAULT_CONFIG } from '../../config';
|
|
|
20
21
|
RouterLink,
|
|
21
22
|
],
|
|
22
23
|
})
|
|
23
|
-
export class ConfirmRegisterComponent implements OnInit {
|
|
24
|
+
export class ConfirmRegisterComponent extends BaseAsync implements OnInit {
|
|
24
25
|
private auth = inject(BaseAuthService);
|
|
25
26
|
private fb = inject(FormBuilder);
|
|
26
27
|
private router = inject(Router);
|
|
27
28
|
private route = inject(ActivatedRoute);
|
|
28
29
|
protected readonly guiConfig: SailGuiConfig = inject(SAIL_GUI_CONFIG, {optional: true}) ?? DEFAULT_CONFIG;
|
|
29
30
|
|
|
30
|
-
errorMessage = signal('');
|
|
31
|
-
successMessage = signal('');
|
|
32
|
-
|
|
33
31
|
confirmForm = this.fb.group({
|
|
34
32
|
email: ['', [Validators.required, Validators.email]],
|
|
35
33
|
code: ['', Validators.required],
|
|
@@ -43,15 +41,13 @@ export class ConfirmRegisterComponent implements OnInit {
|
|
|
43
41
|
}
|
|
44
42
|
|
|
45
43
|
confirm(): void {
|
|
46
|
-
this.errorMessage.set('');
|
|
47
|
-
this.successMessage.set('');
|
|
48
|
-
|
|
49
44
|
if (this.confirmForm.invalid) return;
|
|
50
45
|
|
|
51
46
|
const { email, code } = this.confirmForm.value;
|
|
52
47
|
|
|
53
|
-
this.
|
|
54
|
-
|
|
48
|
+
this.run(
|
|
49
|
+
this.auth.confirmRegister(email!, code!),
|
|
50
|
+
(resp) => {
|
|
55
51
|
this.successMessage.set('Registration confirmed.');
|
|
56
52
|
if (resp?.paymentRequired && resp.paymentUrl) {
|
|
57
53
|
// Paid plan: go straight to payment. Subscription is 'P' until paid.
|
|
@@ -63,9 +59,7 @@ export class ConfirmRegisterComponent implements OnInit {
|
|
|
63
59
|
// Free plan: land on login.
|
|
64
60
|
this.router.navigate(['/login/local']);
|
|
65
61
|
},
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
},
|
|
69
|
-
});
|
|
62
|
+
'Confirmation failed. Please try again.',
|
|
63
|
+
);
|
|
70
64
|
}
|
|
71
65
|
}
|
|
@@ -22,7 +22,7 @@ import { SAIL_GUI_CONFIG, SailGuiConfig, DEFAULT_CONFIG } from '../../config';
|
|
|
22
22
|
* See keel/README.md → "Consent Capture" for the backend-side contract.
|
|
23
23
|
*/
|
|
24
24
|
@Component({
|
|
25
|
-
selector: '
|
|
25
|
+
selector: 'sail-consent-gate',
|
|
26
26
|
templateUrl: './consent_gate.html',
|
|
27
27
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
28
28
|
encapsulation: ViewEncapsulation.None,
|
|
@@ -18,13 +18,13 @@
|
|
|
18
18
|
<mat-label>Password</mat-label>
|
|
19
19
|
<input matInput type="password" formControlName="password" required>
|
|
20
20
|
</mat-form-field>
|
|
21
|
-
<button
|
|
21
|
+
<button matButton="elevated" class="primary" type="submit" [disabled]="!loginForm.valid">
|
|
22
22
|
Login
|
|
23
23
|
</button>
|
|
24
24
|
</form>
|
|
25
25
|
@if (showSocialLogin()) {
|
|
26
26
|
<hr class="auth-divider">
|
|
27
|
-
<
|
|
27
|
+
<sail-social-login
|
|
28
28
|
[providers]="enabledSocialProviders()"
|
|
29
29
|
(loginSuccess)="onSocialSuccess($event)"
|
|
30
30
|
(loginError)="onSocialError($event)" />
|
|
@@ -10,7 +10,7 @@ import { LoginResponseSocial, SocialProvider } from '../../model/auth';
|
|
|
10
10
|
import { SocialLoginComponent } from './social_login';
|
|
11
11
|
|
|
12
12
|
@Component({
|
|
13
|
-
selector: '
|
|
13
|
+
selector: 'sail-login',
|
|
14
14
|
templateUrl: './login_component.html',
|
|
15
15
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
16
16
|
encapsulation: ViewEncapsulation.None,
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
<p class="otp-resend">
|
|
18
18
|
@if (canResend()) {
|
|
19
|
-
<button
|
|
19
|
+
<button matButton class="resend-btn" type="button" (click)="onResend()">Resend code</button>
|
|
20
20
|
} @else {
|
|
21
21
|
Resend code in {{ countdown() }}s
|
|
22
22
|
}
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
@if (key === '') {
|
|
28
28
|
<div class="keypad-spacer"></div>
|
|
29
29
|
} @else if (key === 'back') {
|
|
30
|
-
<button
|
|
30
|
+
<button matButton class="keypad-key" type="button" (click)="onBackspace()" aria-label="Backspace" [disabled]="disabled()">
|
|
31
31
|
<mat-icon>backspace</mat-icon>
|
|
32
32
|
</button>
|
|
33
33
|
} @else {
|
|
34
|
-
<button
|
|
34
|
+
<button matButton class="keypad-key" type="button" (click)="onKeyPress(key)" [disabled]="disabled()">{{ key }}</button>
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
</div>
|
|
@@ -13,7 +13,7 @@ import { MatIconModule } from '@angular/material/icon';
|
|
|
13
13
|
* call verifyOtp / resendOtp. See README "Phone / email OTP login".
|
|
14
14
|
*/
|
|
15
15
|
@Component({
|
|
16
|
-
selector: '
|
|
16
|
+
selector: 'sail-otp-input',
|
|
17
17
|
templateUrl: './otp_input.html',
|
|
18
18
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
19
19
|
encapsulation: ViewEncapsulation.None,
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
<div class="geocode-status geocode-error" role="alert">{{ registerError() }}</div>
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
<button
|
|
123
|
+
<button matButton="elevated" class="primary" type="submit"
|
|
124
124
|
[disabled]="!registerForm.valid || geocodeStatus() !== 'success'">
|
|
125
125
|
Create Account
|
|
126
126
|
</button>
|
|
@@ -15,7 +15,7 @@ import { PartnerRegistration, PublicPlan } from '../../model/appdata';
|
|
|
15
15
|
import { SAIL_GUI_CONFIG, SailGuiConfig, DEFAULT_CONFIG } from '../../config';
|
|
16
16
|
|
|
17
17
|
@Component({
|
|
18
|
-
selector: '
|
|
18
|
+
selector: 'sail-register',
|
|
19
19
|
templateUrl: './register_component.html',
|
|
20
20
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
21
21
|
encapsulation: ViewEncapsulation.None,
|
|
@@ -27,7 +27,7 @@ const APPLE_URL = 'https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/
|
|
|
27
27
|
* without config are hidden (with a console.warn for diagnostics).
|
|
28
28
|
*/
|
|
29
29
|
@Component({
|
|
30
|
-
selector: '
|
|
30
|
+
selector: 'sail-social-login',
|
|
31
31
|
templateUrl: './social_login.html',
|
|
32
32
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
33
33
|
encapsulation: ViewEncapsulation.None,
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<mat-sidenav-content>
|
|
30
30
|
<mat-toolbar class="primary">
|
|
31
31
|
@if (isHandset()) {
|
|
32
|
-
<button type="button" aria-label="Toggle sidenav"
|
|
32
|
+
<button type="button" aria-label="Toggle sidenav" matIconButton (click)="sidenav.toggle()">
|
|
33
33
|
<mat-icon aria-label="Side nav toggle icon">menu</mat-icon>
|
|
34
34
|
</button>
|
|
35
35
|
}
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
<span class="toolbar-spacer"></span>
|
|
38
38
|
<ng-content select="[toolbar-actions]"></ng-content>
|
|
39
39
|
@if (isLoggedIn()) {
|
|
40
|
-
<button
|
|
40
|
+
<button matButton (click)="logout()">Logout</button>
|
|
41
41
|
} @else if (guiConfig.publicRouteLinks) {
|
|
42
42
|
@for (link of guiConfig.publicRouteLinks; track link.routerLink) {
|
|
43
|
-
<a
|
|
43
|
+
<a matButton [routerLink]="link.routerLink">{{ link.label }}</a>
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
</mat-toolbar>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChangeDetectionStrategy, Component, computed, inject,
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, inject, ViewEncapsulation } from "@angular/core";
|
|
2
2
|
import { toSignal } from "@angular/core/rxjs-interop";
|
|
3
3
|
import { RouterLink, RouterOutlet } from "@angular/router";
|
|
4
4
|
import { MatSidenavModule } from '@angular/material/sidenav';
|
|
@@ -14,7 +14,7 @@ import { BaseAuthService } from "../../service/auth.service";
|
|
|
14
14
|
import { SAIL_GUI_CONFIG, SailGuiConfig, DEFAULT_CONFIG } from "../../config";
|
|
15
15
|
|
|
16
16
|
@Component({
|
|
17
|
-
selector: '
|
|
17
|
+
selector: 'sail-navigation',
|
|
18
18
|
templateUrl: './navigation.html',
|
|
19
19
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
20
20
|
encapsulation: ViewEncapsulation.None,
|
|
@@ -29,16 +29,20 @@ import { SAIL_GUI_CONFIG, SailGuiConfig, DEFAULT_CONFIG } from "../../config";
|
|
|
29
29
|
MatExpansionModule,
|
|
30
30
|
],
|
|
31
31
|
})
|
|
32
|
-
export class Navigation
|
|
32
|
+
export class Navigation {
|
|
33
33
|
private readonly authService = inject(BaseAuthService);
|
|
34
34
|
private readonly breakpointObserver = inject(BreakpointObserver);
|
|
35
35
|
protected readonly guiConfig: SailGuiConfig = inject(SAIL_GUI_CONFIG, {optional: true}) ?? DEFAULT_CONFIG;
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
// Restore the stored JWT before the menu stream fires so `canAccess()` checks
|
|
38
|
+
// resolve against the right user. Done at field-init time (runs in injection
|
|
39
|
+
// context) rather than ngOnInit, so the toSignal below can read from it.
|
|
40
|
+
private readonly _sessionLoaded = (this.authService.loadStoredSession(), true);
|
|
41
|
+
|
|
42
|
+
private readonly allMenuItems = toSignal(this.authService.getMenus(), { initialValue: [] as ApplicationMenu[] });
|
|
38
43
|
|
|
39
44
|
readonly menuItems = computed(() => {
|
|
40
|
-
|
|
41
|
-
return menus
|
|
45
|
+
return this.allMenuItems()
|
|
42
46
|
.map(menu => ({
|
|
43
47
|
...menu,
|
|
44
48
|
ApplicationMenuItems: (menu.ApplicationMenuItems ?? [])
|
|
@@ -54,11 +58,6 @@ export class Navigation implements OnInit {
|
|
|
54
58
|
|
|
55
59
|
readonly isLoggedIn = this.authService.isLoggedIn;
|
|
56
60
|
|
|
57
|
-
ngOnInit() {
|
|
58
|
-
this.authService.loadStoredSession();
|
|
59
|
-
this.authService.getMenus().subscribe((menuItems: ApplicationMenu[]) => { this.allMenuItems.set(menuItems); });
|
|
60
|
-
}
|
|
61
|
-
|
|
62
61
|
logout() {
|
|
63
62
|
this.authService.logout();
|
|
64
63
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<div class="user-payment-methods">
|
|
2
|
+
<h2 class="user-payment-methods__title">{{ title() }}</h2>
|
|
3
|
+
|
|
4
|
+
@if (showAdd()) {
|
|
5
|
+
<button matButton="elevated"
|
|
6
|
+
class="user-payment-methods__add accent"
|
|
7
|
+
(click)="addClicked.emit()">
|
|
8
|
+
<mat-icon>add</mat-icon> {{ addLabel() }}
|
|
9
|
+
</button>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@if (methods().length === 0 && !loading()) {
|
|
13
|
+
<p class="user-payment-methods__empty">{{ emptyLabel() }}</p>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@for (m of methods(); track m.Id) {
|
|
17
|
+
<mat-card class="user-payment-methods__item">
|
|
18
|
+
<mat-card-content>
|
|
19
|
+
<div class="user-payment-methods__row">
|
|
20
|
+
<div class="user-payment-methods__info">
|
|
21
|
+
<mat-icon>{{ iconFor(m) }}</mat-icon>
|
|
22
|
+
<span class="user-payment-methods__brand">{{ m.Brand || m.MethodType }}</span>
|
|
23
|
+
@if (m.LastFour) {
|
|
24
|
+
<span class="user-payment-methods__last4">•••• {{ m.LastFour }}</span>
|
|
25
|
+
}
|
|
26
|
+
@if (m.IsDefault) {
|
|
27
|
+
<span class="user-payment-methods__default">Default</span>
|
|
28
|
+
}
|
|
29
|
+
</div>
|
|
30
|
+
<div class="user-payment-methods__actions">
|
|
31
|
+
@if (!m.IsDefault) {
|
|
32
|
+
<button matButton (click)="setDefault(m)">Set default</button>
|
|
33
|
+
}
|
|
34
|
+
<button matIconButton (click)="remove(m)" aria-label="Delete payment method">
|
|
35
|
+
<mat-icon>delete</mat-icon>
|
|
36
|
+
</button>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</mat-card-content>
|
|
40
|
+
</mat-card>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@if (errorMsg()) {
|
|
44
|
+
<p class="user-payment-methods__error">{{ errorMsg() }}</p>
|
|
45
|
+
}
|
|
46
|
+
</div>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { ChangeDetectionStrategy, Component,
|
|
2
|
-
import { CommonModule } from '@angular/common';
|
|
1
|
+
import { ChangeDetectionStrategy, Component, OnInit, ViewEncapsulation, inject, input, output, signal } from '@angular/core';
|
|
3
2
|
import { MatButtonModule } from '@angular/material/button';
|
|
4
3
|
import { MatCardModule } from '@angular/material/card';
|
|
5
4
|
import { MatIconModule } from '@angular/material/icon';
|
|
@@ -23,55 +22,8 @@ import { UserPaymentMethod } from '../../model/appdata';
|
|
|
23
22
|
selector: 'sail-user-payment-methods',
|
|
24
23
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
25
24
|
encapsulation: ViewEncapsulation.None,
|
|
26
|
-
imports: [
|
|
27
|
-
|
|
28
|
-
<div class="user-payment-methods">
|
|
29
|
-
<h2 class="user-payment-methods__title">{{ title }}</h2>
|
|
30
|
-
|
|
31
|
-
@if (showAdd) {
|
|
32
|
-
<button mat-raised-button color="accent"
|
|
33
|
-
class="user-payment-methods__add"
|
|
34
|
-
(click)="addClicked.emit()">
|
|
35
|
-
<mat-icon>add</mat-icon> {{ addLabel }}
|
|
36
|
-
</button>
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
@if (methods().length === 0 && !loading()) {
|
|
40
|
-
<p class="user-payment-methods__empty">{{ emptyLabel }}</p>
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
@for (m of methods(); track m.Id) {
|
|
44
|
-
<mat-card class="user-payment-methods__item">
|
|
45
|
-
<mat-card-content>
|
|
46
|
-
<div class="user-payment-methods__row">
|
|
47
|
-
<div class="user-payment-methods__info">
|
|
48
|
-
<mat-icon>{{ iconFor(m) }}</mat-icon>
|
|
49
|
-
<span class="user-payment-methods__brand">{{ m.Brand || m.MethodType }}</span>
|
|
50
|
-
@if (m.LastFour) {
|
|
51
|
-
<span class="user-payment-methods__last4">•••• {{ m.LastFour }}</span>
|
|
52
|
-
}
|
|
53
|
-
@if (m.IsDefault) {
|
|
54
|
-
<span class="user-payment-methods__default">Default</span>
|
|
55
|
-
}
|
|
56
|
-
</div>
|
|
57
|
-
<div class="user-payment-methods__actions">
|
|
58
|
-
@if (!m.IsDefault) {
|
|
59
|
-
<button mat-button (click)="setDefault(m)">Set default</button>
|
|
60
|
-
}
|
|
61
|
-
<button mat-icon-button (click)="remove(m)" aria-label="Delete payment method">
|
|
62
|
-
<mat-icon>delete</mat-icon>
|
|
63
|
-
</button>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
</mat-card-content>
|
|
67
|
-
</mat-card>
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
@if (errorMsg()) {
|
|
71
|
-
<p class="user-payment-methods__error">{{ errorMsg() }}</p>
|
|
72
|
-
}
|
|
73
|
-
</div>
|
|
74
|
-
`,
|
|
25
|
+
imports: [MatButtonModule, MatCardModule, MatIconModule],
|
|
26
|
+
templateUrl: './user_payment_methods.html',
|
|
75
27
|
styles: `
|
|
76
28
|
.user-payment-methods { display: flex; flex-direction: column; gap: 12px; }
|
|
77
29
|
.user-payment-methods__title { margin: 0; }
|
|
@@ -88,17 +40,17 @@ import { UserPaymentMethod } from '../../model/appdata';
|
|
|
88
40
|
`,
|
|
89
41
|
})
|
|
90
42
|
export class UserPaymentMethodsComponent extends BaseAsync implements OnInit {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
43
|
+
readonly title = input('Payment Methods');
|
|
44
|
+
readonly addLabel = input('Add Payment Method');
|
|
45
|
+
readonly emptyLabel = input('No saved payment methods yet.');
|
|
46
|
+
readonly showAdd = input(true);
|
|
95
47
|
|
|
96
48
|
/** Emitted when the "Add" button is tapped. Consumer routes to its SetupIntent flow. */
|
|
97
|
-
|
|
49
|
+
readonly addClicked = output<void>();
|
|
98
50
|
/** Emitted after a successful set-default flip. */
|
|
99
|
-
|
|
51
|
+
readonly defaultChanged = output<UserPaymentMethod>();
|
|
100
52
|
/** Emitted after a successful delete. */
|
|
101
|
-
|
|
53
|
+
readonly deleted = output<UserPaymentMethod>();
|
|
102
54
|
|
|
103
55
|
private readonly service = inject(UserPaymentMethodService);
|
|
104
56
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<div class="bank-info-form">
|
|
2
|
+
<h2 class="bank-info-form__title">{{ title() }}</h2>
|
|
3
|
+
|
|
4
|
+
@if (intro()) {
|
|
5
|
+
<p class="bank-info-form__intro">{{ intro() }}</p>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
<form [formGroup]="form" (ngSubmit)="submit()">
|
|
9
|
+
<mat-form-field appearance="outline" class="full-width">
|
|
10
|
+
<mat-label>Country *</mat-label>
|
|
11
|
+
<mat-select formControlName="countryCode">
|
|
12
|
+
@for (c of countryProfiles(); track c.code) {
|
|
13
|
+
<mat-option [value]="c.code">{{ c.label }} ({{ c.currency }})</mat-option>
|
|
14
|
+
}
|
|
15
|
+
</mat-select>
|
|
16
|
+
</mat-form-field>
|
|
17
|
+
<p class="hint">Sets your tax ID format + payout currency</p>
|
|
18
|
+
|
|
19
|
+
<mat-form-field appearance="outline" class="full-width">
|
|
20
|
+
<mat-label>Account holder name *</mat-label>
|
|
21
|
+
<input matInput formControlName="accountHolderName">
|
|
22
|
+
</mat-form-field>
|
|
23
|
+
<p class="hint">Legal name on the bank account (must match government ID)</p>
|
|
24
|
+
|
|
25
|
+
<mat-form-field appearance="outline" class="full-width">
|
|
26
|
+
<mat-label>{{ taxIdLabel() }} *</mat-label>
|
|
27
|
+
<input matInput formControlName="taxId" [placeholder]="taxIdPlaceholder()">
|
|
28
|
+
</mat-form-field>
|
|
29
|
+
<p class="hint">{{ taxIdHint() }}</p>
|
|
30
|
+
|
|
31
|
+
<mat-form-field appearance="outline" class="full-width">
|
|
32
|
+
<mat-label>Billing address *</mat-label>
|
|
33
|
+
<input matInput formControlName="billingAddress">
|
|
34
|
+
</mat-form-field>
|
|
35
|
+
<p class="hint">Business or home address — used for tax correspondence</p>
|
|
36
|
+
|
|
37
|
+
<p class="provider-note">
|
|
38
|
+
Bank routing details (account number + institution / ABA /
|
|
39
|
+
IBAN) are collected by the payout provider in the next step;
|
|
40
|
+
this application does not store them.
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
<mat-checkbox formControlName="providerAgreement" class="agreement">
|
|
44
|
+
{{ agreementLabel() }}
|
|
45
|
+
</mat-checkbox>
|
|
46
|
+
|
|
47
|
+
<div class="nav-buttons">
|
|
48
|
+
@if (showBack()) {
|
|
49
|
+
<button matButton="outlined" class="pill-btn" type="button" (click)="back.emit()">
|
|
50
|
+
< Back
|
|
51
|
+
</button>
|
|
52
|
+
}
|
|
53
|
+
<button matButton="filled" class="pill-btn primary-btn" type="submit"
|
|
54
|
+
[disabled]="form.invalid">
|
|
55
|
+
{{ submitLabel() }}
|
|
56
|
+
</button>
|
|
57
|
+
</div>
|
|
58
|
+
</form>
|
|
59
|
+
</div>
|