@nauticana/sail 0.7.0 → 0.8.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/README.md +300 -1
- package/package.json +2 -2
- package/src/component/abstract/base_table.ts +19 -19
- package/src/component/abstract/base_view.ts +9 -9
- package/src/component/billing/checkout_button.html +1 -1
- package/src/component/billing/plan_selector.html +1 -1
- package/src/component/form/form_field.html +1 -1
- package/src/component/form/form_field.ts +7 -3
- package/src/component/form/form_table.html +4 -4
- package/src/component/form/form_table.ts +1 -1
- package/src/component/login/account_deletion.html +1 -1
- package/src/component/login/account_deletion.ts +4 -1
- package/src/component/login/chpass_component.html +1 -1
- package/src/component/login/confirm_chpass_component.html +1 -1
- package/src/component/login/confirm_register_component.html +1 -1
- package/src/component/login/login_component.html +1 -1
- package/src/component/login/otp_input.html +3 -3
- package/src/component/login/register_component.html +1 -1
- package/src/component/navigation/navigation.html +3 -3
- 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/twofactor_setup.html +4 -4
- package/src/component/security/twofactor_verify.html +1 -1
- package/src/component/table/table_detail.html +9 -9
- package/src/component/table/table_detail.ts +10 -3
- package/src/component/table/table_edit.html +8 -8
- package/src/component/table/table_edit.ts +25 -14
- package/src/component/table/table_list.html +5 -5
- package/src/component/table/table_list.ts +17 -7
- package/src/component/table/table_lookup.html +4 -4
- package/src/component/table/table_lookup.ts +10 -9
- package/src/component/table/table_report.html +1 -1
- package/src/component/table/table_search.html +5 -5
- package/src/component/table/table_search.ts +30 -17
|
@@ -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>
|
|
@@ -18,7 +18,7 @@
|
|
|
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>
|
|
@@ -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>
|
|
@@ -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>
|
|
@@ -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>
|
|
@@ -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>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ChangeDetectionStrategy, Component,
|
|
1
|
+
import { ChangeDetectionStrategy, Component, computed, inject, input, output, signal } from '@angular/core';
|
|
2
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
2
3
|
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
3
4
|
import { MatButtonModule } from '@angular/material/button';
|
|
4
5
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
@@ -40,67 +41,7 @@ export const DEFAULT_COUNTRY_PROFILES: CountryProfile[] = [
|
|
|
40
41
|
ReactiveFormsModule, MatButtonModule, MatCheckboxModule,
|
|
41
42
|
MatFormFieldModule, MatInputModule, MatSelectModule,
|
|
42
43
|
],
|
|
43
|
-
|
|
44
|
-
<div class="bank-info-form">
|
|
45
|
-
<h2 class="bank-info-form__title">{{ title }}</h2>
|
|
46
|
-
|
|
47
|
-
@if (intro) {
|
|
48
|
-
<p class="bank-info-form__intro">{{ intro }}</p>
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
<form [formGroup]="form" (ngSubmit)="submit()">
|
|
52
|
-
<mat-form-field appearance="outline" class="full-width">
|
|
53
|
-
<mat-label>Country *</mat-label>
|
|
54
|
-
<mat-select formControlName="countryCode">
|
|
55
|
-
@for (c of countryProfiles; track c.code) {
|
|
56
|
-
<mat-option [value]="c.code">{{ c.label }} ({{ c.currency }})</mat-option>
|
|
57
|
-
}
|
|
58
|
-
</mat-select>
|
|
59
|
-
</mat-form-field>
|
|
60
|
-
<p class="hint">Sets your tax ID format + payout currency</p>
|
|
61
|
-
|
|
62
|
-
<mat-form-field appearance="outline" class="full-width">
|
|
63
|
-
<mat-label>Account holder name *</mat-label>
|
|
64
|
-
<input matInput formControlName="accountHolderName">
|
|
65
|
-
</mat-form-field>
|
|
66
|
-
<p class="hint">Legal name on the bank account (must match government ID)</p>
|
|
67
|
-
|
|
68
|
-
<mat-form-field appearance="outline" class="full-width">
|
|
69
|
-
<mat-label>{{ taxIdLabel() }} *</mat-label>
|
|
70
|
-
<input matInput formControlName="taxId" [placeholder]="taxIdPlaceholder()">
|
|
71
|
-
</mat-form-field>
|
|
72
|
-
<p class="hint">{{ taxIdHint() }}</p>
|
|
73
|
-
|
|
74
|
-
<mat-form-field appearance="outline" class="full-width">
|
|
75
|
-
<mat-label>Billing address *</mat-label>
|
|
76
|
-
<input matInput formControlName="billingAddress">
|
|
77
|
-
</mat-form-field>
|
|
78
|
-
<p class="hint">Business or home address — used for tax correspondence</p>
|
|
79
|
-
|
|
80
|
-
<p class="provider-note">
|
|
81
|
-
Bank routing details (account number + institution / ABA /
|
|
82
|
-
IBAN) are collected by the payout provider in the next step;
|
|
83
|
-
this application does not store them.
|
|
84
|
-
</p>
|
|
85
|
-
|
|
86
|
-
<mat-checkbox formControlName="providerAgreement" class="agreement">
|
|
87
|
-
{{ agreementLabel }}
|
|
88
|
-
</mat-checkbox>
|
|
89
|
-
|
|
90
|
-
<div class="nav-buttons">
|
|
91
|
-
@if (showBack) {
|
|
92
|
-
<button mat-stroked-button class="pill-btn" type="button" (click)="back.emit()">
|
|
93
|
-
< Back
|
|
94
|
-
</button>
|
|
95
|
-
}
|
|
96
|
-
<button mat-flat-button class="pill-btn primary-btn" type="submit"
|
|
97
|
-
[disabled]="form.invalid">
|
|
98
|
-
{{ submitLabel }}
|
|
99
|
-
</button>
|
|
100
|
-
</div>
|
|
101
|
-
</form>
|
|
102
|
-
</div>
|
|
103
|
-
`,
|
|
44
|
+
templateUrl: './bank_info_form.html',
|
|
104
45
|
styles: `
|
|
105
46
|
.bank-info-form { display: flex; flex-direction: column; gap: 4px; padding: 0 24px 32px; }
|
|
106
47
|
.bank-info-form__title { margin: 0 0 16px; }
|
|
@@ -116,24 +57,24 @@ export const DEFAULT_COUNTRY_PROFILES: CountryProfile[] = [
|
|
|
116
57
|
})
|
|
117
58
|
export class PayoutBankInfoFormComponent {
|
|
118
59
|
/** Step title rendered above the form. */
|
|
119
|
-
|
|
60
|
+
readonly title = input('Payment and Tax');
|
|
120
61
|
/** Optional explainer rendered below the title. */
|
|
121
|
-
|
|
62
|
+
readonly intro = input('Tax + payout details. Bank account itself is set up with the payout provider in a separate step — only tax-reporting and dispute-correspondence fields are collected here.');
|
|
122
63
|
/** Override the list of supported countries — keep the default for most apps. */
|
|
123
|
-
|
|
64
|
+
readonly countryProfiles = input<CountryProfile[]>(DEFAULT_COUNTRY_PROFILES);
|
|
124
65
|
/** Provider code persisted on user_bank_info.provider. Default AW (Airwallex) matches keel's default --payout_provider. */
|
|
125
|
-
|
|
66
|
+
readonly provider = input('AW');
|
|
126
67
|
/** Wording on the provider-agreement checkbox. Override to name the live provider. */
|
|
127
|
-
|
|
68
|
+
readonly agreementLabel = input('I agree to the payout provider account agreement');
|
|
128
69
|
/** Submit-button label — wizards usually use "Next >". */
|
|
129
|
-
|
|
70
|
+
readonly submitLabel = input('Next >');
|
|
130
71
|
/** When true, the back button renders alongside submit. */
|
|
131
|
-
|
|
72
|
+
readonly showBack = input(true);
|
|
132
73
|
|
|
133
74
|
/** Emitted on submit; payload maps 1:1 to basis user_bank_info columns. */
|
|
134
|
-
|
|
75
|
+
readonly submitted = output<BankInfoFormValue>();
|
|
135
76
|
/** Emitted when the user clicks Back. */
|
|
136
|
-
|
|
77
|
+
readonly back = output<void>();
|
|
137
78
|
|
|
138
79
|
private readonly fb = inject(FormBuilder);
|
|
139
80
|
|
|
@@ -145,17 +86,20 @@ export class PayoutBankInfoFormComponent {
|
|
|
145
86
|
providerAgreement: [false, Validators.requiredTrue],
|
|
146
87
|
});
|
|
147
88
|
|
|
148
|
-
private readonly selectedCountry = signal<CountryProfile>(this.countryProfiles[0]);
|
|
89
|
+
private readonly selectedCountry = signal<CountryProfile>(this.countryProfiles()[0]);
|
|
149
90
|
|
|
150
91
|
readonly taxIdLabel = computed(() => this.selectedCountry().taxIdLabel);
|
|
151
92
|
readonly taxIdPlaceholder = computed(() => this.selectedCountry().taxIdPlaceholder);
|
|
152
93
|
readonly taxIdHint = computed(() => this.selectedCountry().taxIdHint);
|
|
153
94
|
|
|
154
95
|
constructor() {
|
|
155
|
-
this.form.controls.countryCode.valueChanges
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
96
|
+
this.form.controls.countryCode.valueChanges
|
|
97
|
+
.pipe(takeUntilDestroyed())
|
|
98
|
+
.subscribe((code) => {
|
|
99
|
+
const profiles = this.countryProfiles();
|
|
100
|
+
const profile = profiles.find((c) => c.code === code) ?? profiles[0];
|
|
101
|
+
this.selectedCountry.set(profile);
|
|
102
|
+
});
|
|
159
103
|
}
|
|
160
104
|
|
|
161
105
|
submit() {
|
|
@@ -169,7 +113,7 @@ export class PayoutBankInfoFormComponent {
|
|
|
169
113
|
billingAddress: v.billingAddress!,
|
|
170
114
|
taxIdType: country.taxIdType,
|
|
171
115
|
taxId: v.taxId!,
|
|
172
|
-
provider: this.provider,
|
|
116
|
+
provider: this.provider(),
|
|
173
117
|
providerAgreement: v.providerAgreement!,
|
|
174
118
|
});
|
|
175
119
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<div class="payout-onboarding">
|
|
2
|
+
<h2 class="payout-onboarding__title">{{ title() }}</h2>
|
|
3
|
+
|
|
4
|
+
<p class="payout-onboarding__intro">
|
|
5
|
+
Bank routing details are collected by the payout provider, not
|
|
6
|
+
this application. After this step the provider will run KYC and
|
|
7
|
+
notify us when the account is ready for payouts.
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
@if (reusable().length > 0) {
|
|
11
|
+
<mat-card class="reuse-card">
|
|
12
|
+
<mat-card-header>
|
|
13
|
+
<mat-card-title>Reuse an existing account</mat-card-title>
|
|
14
|
+
</mat-card-header>
|
|
15
|
+
<mat-card-content>
|
|
16
|
+
<p class="hint">
|
|
17
|
+
You already have a payout account on
|
|
18
|
+
{{ reusable().length === 1 ? 'another partner' : 'other partners' }}.
|
|
19
|
+
You can reuse it here (no KYC redo) if the currency matches.
|
|
20
|
+
</p>
|
|
21
|
+
<mat-radio-group [value]="selectedAccountId()" (change)="selectAccount($any($event.value))">
|
|
22
|
+
@for (acc of reusable(); track acc.providerAccountId) {
|
|
23
|
+
<mat-radio-button [value]="acc.providerAccountId" class="reuse-option">
|
|
24
|
+
<strong>{{ acc.partnerCaption }}</strong>
|
|
25
|
+
— {{ acc.countryCode }} / {{ acc.currency }} ({{ acc.provider }})
|
|
26
|
+
</mat-radio-button>
|
|
27
|
+
}
|
|
28
|
+
</mat-radio-group>
|
|
29
|
+
<button matButton="filled" class="payout-onboarding__btn payout-onboarding__btn--primary"
|
|
30
|
+
[disabled]="!selectedAccountId() || busy()"
|
|
31
|
+
(click)="linkExisting()">
|
|
32
|
+
Use this account
|
|
33
|
+
</button>
|
|
34
|
+
</mat-card-content>
|
|
35
|
+
</mat-card>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
<mat-card>
|
|
39
|
+
<mat-card-header>
|
|
40
|
+
<mat-card-title>Set up a new account</mat-card-title>
|
|
41
|
+
</mat-card-header>
|
|
42
|
+
<mat-card-content>
|
|
43
|
+
<p class="hint">
|
|
44
|
+
Opens the provider's secure onboarding page. You'll be asked
|
|
45
|
+
for bank routing details and government ID. The process
|
|
46
|
+
usually takes a few minutes.
|
|
47
|
+
</p>
|
|
48
|
+
@if (errorMsg()) {
|
|
49
|
+
<p class="payout-onboarding__error">{{ errorMsg() }}</p>
|
|
50
|
+
}
|
|
51
|
+
<button matButton="filled" class="payout-onboarding__btn payout-onboarding__btn--primary"
|
|
52
|
+
[disabled]="busy()"
|
|
53
|
+
(click)="startProviderKyc()">
|
|
54
|
+
<mat-icon>open_in_new</mat-icon>
|
|
55
|
+
Start onboarding
|
|
56
|
+
</button>
|
|
57
|
+
</mat-card-content>
|
|
58
|
+
</mat-card>
|
|
59
|
+
|
|
60
|
+
<div class="payout-onboarding__nav">
|
|
61
|
+
@if (showBack()) {
|
|
62
|
+
<button matButton="outlined" class="payout-onboarding__btn" type="button" (click)="back.emit()">
|
|
63
|
+
< Back
|
|
64
|
+
</button>
|
|
65
|
+
}
|
|
66
|
+
<button matButton="outlined" class="payout-onboarding__btn" type="button" (click)="skipped.emit()">
|
|
67
|
+
{{ skipLabel() }}
|
|
68
|
+
</button>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChangeDetectionStrategy, Component,
|
|
1
|
+
import { ChangeDetectionStrategy, Component, OnInit, inject, input, output, signal } from '@angular/core';
|
|
2
2
|
import { MatButtonModule } from '@angular/material/button';
|
|
3
3
|
import { MatCardModule } from '@angular/material/card';
|
|
4
4
|
import { MatIconModule } from '@angular/material/icon';
|
|
@@ -26,78 +26,7 @@ import { ReusableAccount } from '../../model/appdata';
|
|
|
26
26
|
selector: 'sail-payout-provider-onboarding',
|
|
27
27
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
28
28
|
imports: [MatButtonModule, MatCardModule, MatIconModule, MatRadioModule],
|
|
29
|
-
|
|
30
|
-
<div class="payout-onboarding">
|
|
31
|
-
<h2 class="payout-onboarding__title">{{ title }}</h2>
|
|
32
|
-
|
|
33
|
-
<p class="payout-onboarding__intro">
|
|
34
|
-
Bank routing details are collected by the payout provider, not
|
|
35
|
-
this application. After this step the provider will run KYC and
|
|
36
|
-
notify us when the account is ready for payouts.
|
|
37
|
-
</p>
|
|
38
|
-
|
|
39
|
-
@if (reusable().length > 0) {
|
|
40
|
-
<mat-card class="reuse-card">
|
|
41
|
-
<mat-card-header>
|
|
42
|
-
<mat-card-title>Reuse an existing account</mat-card-title>
|
|
43
|
-
</mat-card-header>
|
|
44
|
-
<mat-card-content>
|
|
45
|
-
<p class="hint">
|
|
46
|
-
You already have a payout account on
|
|
47
|
-
{{ reusable().length === 1 ? 'another partner' : 'other partners' }}.
|
|
48
|
-
You can reuse it here (no KYC redo) if the currency matches.
|
|
49
|
-
</p>
|
|
50
|
-
<mat-radio-group [value]="selectedAccountId()" (change)="selectAccount($any($event.value))">
|
|
51
|
-
@for (acc of reusable(); track acc.providerAccountId) {
|
|
52
|
-
<mat-radio-button [value]="acc.providerAccountId" class="reuse-option">
|
|
53
|
-
<strong>{{ acc.partnerCaption }}</strong>
|
|
54
|
-
— {{ acc.countryCode }} / {{ acc.currency }} ({{ acc.provider }})
|
|
55
|
-
</mat-radio-button>
|
|
56
|
-
}
|
|
57
|
-
</mat-radio-group>
|
|
58
|
-
<button mat-flat-button class="payout-onboarding__btn payout-onboarding__btn--primary"
|
|
59
|
-
[disabled]="!selectedAccountId() || busy()"
|
|
60
|
-
(click)="linkExisting()">
|
|
61
|
-
Use this account
|
|
62
|
-
</button>
|
|
63
|
-
</mat-card-content>
|
|
64
|
-
</mat-card>
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
<mat-card>
|
|
68
|
-
<mat-card-header>
|
|
69
|
-
<mat-card-title>Set up a new account</mat-card-title>
|
|
70
|
-
</mat-card-header>
|
|
71
|
-
<mat-card-content>
|
|
72
|
-
<p class="hint">
|
|
73
|
-
Opens the provider's secure onboarding page. You'll be asked
|
|
74
|
-
for bank routing details and government ID. The process
|
|
75
|
-
usually takes a few minutes.
|
|
76
|
-
</p>
|
|
77
|
-
@if (errorMsg()) {
|
|
78
|
-
<p class="payout-onboarding__error">{{ errorMsg() }}</p>
|
|
79
|
-
}
|
|
80
|
-
<button mat-flat-button class="payout-onboarding__btn payout-onboarding__btn--primary"
|
|
81
|
-
[disabled]="busy()"
|
|
82
|
-
(click)="startProviderKyc()">
|
|
83
|
-
<mat-icon>open_in_new</mat-icon>
|
|
84
|
-
Start onboarding
|
|
85
|
-
</button>
|
|
86
|
-
</mat-card-content>
|
|
87
|
-
</mat-card>
|
|
88
|
-
|
|
89
|
-
<div class="payout-onboarding__nav">
|
|
90
|
-
@if (showBack) {
|
|
91
|
-
<button mat-stroked-button class="payout-onboarding__btn" type="button" (click)="back.emit()">
|
|
92
|
-
< Back
|
|
93
|
-
</button>
|
|
94
|
-
}
|
|
95
|
-
<button mat-stroked-button class="payout-onboarding__btn" type="button" (click)="skipped.emit()">
|
|
96
|
-
{{ skipLabel }}
|
|
97
|
-
</button>
|
|
98
|
-
</div>
|
|
99
|
-
</div>
|
|
100
|
-
`,
|
|
29
|
+
templateUrl: './provider_onboarding.html',
|
|
101
30
|
styles: `
|
|
102
31
|
.payout-onboarding { display: flex; flex-direction: column; gap: 16px; padding: 0 24px 32px; }
|
|
103
32
|
.payout-onboarding__title { margin: 0; }
|
|
@@ -113,20 +42,20 @@ import { ReusableAccount } from '../../model/appdata';
|
|
|
113
42
|
})
|
|
114
43
|
export class PayoutProviderOnboardingComponent implements OnInit {
|
|
115
44
|
/** Step title — defaults match the most common payout-step framing. */
|
|
116
|
-
|
|
45
|
+
readonly title = input('Bank Account Setup');
|
|
117
46
|
/** Skip-button label — covers wizards where skipping returns to the parent flow. */
|
|
118
|
-
|
|
47
|
+
readonly skipLabel = input("I'll do this later");
|
|
119
48
|
/** When true, the back button renders alongside skip. */
|
|
120
|
-
|
|
49
|
+
readonly showBack = input(true);
|
|
121
50
|
|
|
122
51
|
/** Emitted after a successful reuse-link operation. */
|
|
123
|
-
|
|
52
|
+
readonly linked = output<void>();
|
|
124
53
|
/** Emitted when the user dismisses the step (skip). */
|
|
125
|
-
|
|
54
|
+
readonly skipped = output<void>();
|
|
126
55
|
/** Emitted when the user clicks Back. */
|
|
127
|
-
|
|
56
|
+
readonly back = output<void>();
|
|
128
57
|
/** Emitted after the hosted page is opened in a new tab. Consumers can route away or show a "waiting on provider" screen. */
|
|
129
|
-
|
|
58
|
+
readonly started = output<void>();
|
|
130
59
|
|
|
131
60
|
private readonly payoutService = inject(PayoutService);
|
|
132
61
|
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
<td>{{ formatDate(device.last_used_at) }}</td>
|
|
32
32
|
<td>{{ formatDate(device.created_at) }}</td>
|
|
33
33
|
<td>
|
|
34
|
-
<button
|
|
34
|
+
<button matIconButton (click)="revokeDevice(device)" title="Revoke">
|
|
35
35
|
<mat-icon>delete</mat-icon>
|
|
36
36
|
</button>
|
|
37
37
|
</td>
|
|
@@ -58,15 +58,15 @@
|
|
|
58
58
|
<input matInput type="password" formControlName="password" autocomplete="current-password" required>
|
|
59
59
|
</mat-form-field>
|
|
60
60
|
<div class="auth-actions">
|
|
61
|
-
<button
|
|
61
|
+
<button matButton="elevated" class="warn" type="submit" [disabled]="!logoutForm.valid || loading()">
|
|
62
62
|
Sign out everywhere
|
|
63
63
|
</button>
|
|
64
|
-
<button
|
|
64
|
+
<button matButton type="button" (click)="logoutOpen.set(false)">Cancel</button>
|
|
65
65
|
</div>
|
|
66
66
|
</form>
|
|
67
67
|
} @else {
|
|
68
68
|
<div class="auth-actions">
|
|
69
|
-
<button
|
|
69
|
+
<button matButton class="warn" type="button" (click)="logoutOpen.set(true)" [disabled]="loading()">
|
|
70
70
|
Sign out of all devices
|
|
71
71
|
</button>
|
|
72
72
|
<a routerLink="/dashboard">Back to Dashboard</a>
|