Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/app/parties/parties.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[columns]="columns"
[data]="parties$ | async"
[filter]="initSearchParams$ | async"
[hasMore]="hasMore$ | async"
[progress]="inProgress$ | async"
externalFilter
standaloneFilter
Expand Down
1 change: 1 addition & 0 deletions src/app/parties/parties.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export class PartiesComponent implements OnInit {
parties$ = this.fetchFullDomainObjectsService.result$.pipe(
map((objs) => objs.map((obj) => obj.object.party_config)),
);
hasMore$ = this.fetchFullDomainObjectsService.hasMore$;
columns: Column<PartyConfigObject>[] = [
{
field: 'id',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { EventType } from '@vality/fistful-proto/webhooker';

export const WALLET_EVENT_TYPES: EventType = {
withdrawal: {
started: {},
succeeded: {},
failed: {},
},
destination: {
created: {},
},
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
<v-dialog [progress]="progress()" title="Create wallet webhook">
<cc-fistful-thrift-form
[extensions]="extensions$ | async"
[formControl]="control"
namespace="webhooker"
type="WebhookParams"
></cc-fistful-thrift-form>
<div class="flex flex-col gap-4">
<cc-wallet-merchant-field [formField]="control.partyWallet" />
<v-input-field [formField]="control.url" label="URL" required />
<div class="flex flex-col gap-1.5">
<div class="font-medium text-sm text-neutral-600 dark:text-neutral-300">
Wallet Event Types
</div>
<cc-event-types-field [formField]="control.eventTypes" [structure]="walletEventTypes" />
</div>
</div>
<v-dialog-actions>
<button [disabled]="progress() || control.invalid" mat-button (click)="create()">
<button [disabled]="progress() || control().invalid()" mat-button (click)="create()">
Create
</button>
</v-dialog-actions>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,41 @@
import { distinctUntilChanged, map, of } from 'rxjs';

import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
import { FormField, form, required } from '@angular/forms/signals';
import { MatButtonModule } from '@angular/material/button';

import { WebhookParams } from '@vality/fistful-proto/webhooker';
import { EventType, WebhookParams } from '@vality/fistful-proto/webhooker';
import {
DialogModule,
DialogSuperclass,
InputFieldModule,
NotifyLogService,
getValueChanges,
progressTo,
} from '@vality/matez';
import { isTypeWithAliases } from '@vality/ng-thrift';

import { ThriftWalletWebhooksManagementService } from '~/api/services';
import { FistfulThriftFormComponent } from '~/components/fistful-thrift-form';
import { DomainMetadataFormExtensionsService } from '~/components/thrift-api-crud';
import { EventTypesFieldComponent } from '~/components/event-types-field';
import { PartyWallet, WalletMerchantFieldComponent } from '~/components/wallet-merchant-field';

import { WALLET_EVENT_TYPES } from './consts/wallet-event-types';

interface CreateWalletWebhookModel {
partyWallet: PartyWallet;
url: string;
eventTypes: EventType[];
}

@Component({
selector: 'cc-create-wallet-webhook-dialog',
imports: [
CommonModule,
DialogModule,
FistfulThriftFormComponent,
ReactiveFormsModule,
MatButtonModule,
WalletMerchantFieldComponent,
InputFieldModule,
FormField,
EventTypesFieldComponent,
],
changeDetection: ChangeDetectionStrategy.Eager,
templateUrl: './create-wallet-webhook-dialog.component.html',
Expand All @@ -37,27 +46,37 @@ export class CreateWalletWebhookDialogComponent extends DialogSuperclass<
> {
private walletWebhooksManagementService = inject(ThriftWalletWebhooksManagementService);
private log = inject(NotifyLogService);
private domainMetadataFormExtensionsService = inject(DomainMetadataFormExtensionsService);

control = new FormControl<Partial<WebhookParams>>(
{ party_id: this.dialogData.partyId },
{ nonNullable: true },
);
walletEventTypes = WALLET_EVENT_TYPES as Record<string, unknown>;

controlModel = signal<CreateWalletWebhookModel>({
partyWallet: {
party_id: this.dialogData.partyId,
wallet_id: null,
},
url: '',
eventTypes: [],
});
control = form(this.controlModel, (schemaPath) => {
required(schemaPath.partyWallet.party_id);
required(schemaPath.url);
required(schemaPath.eventTypes);
});
progress = signal(0);
extensions$ = this.domainMetadataFormExtensionsService.createFullDomainObjectsOptionsByType(
'WalletConfigObject',
'wallet_config',
getValueChanges(this.control).pipe(
map((value) => value?.party_id ?? this.dialogData.partyId),
distinctUntilChanged(),
map((partyId) => (obj) => obj.object.wallet_config.data.party_ref.id === partyId),
),
(data) => of(isTypeWithAliases(data, 'WalletID', 'webhooker')),
);

create() {
const { partyWallet, url, eventTypes } = this.controlModel();
const params: WebhookParams = {
party_id: partyWallet.party_id,
...(partyWallet.wallet_id ? { wallet_id: partyWallet.wallet_id } : {}),
url,
event_filter: {
types: new Set(eventTypes),
},
};

this.walletWebhooksManagementService
.Create(this.control.value as WebhookParams)
.Create(params)
.pipe(progressTo(this.progress))
.subscribe(() => {
this.log.success('Webhook created');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { InvoiceEventType } from '@vality/domain-proto/webhooker';

export const SHOP_INVOICE_EVENT_TYPES: InvoiceEventType = {
created: {},
status_changed: {
value: {
unpaid: {},
paid: {},
cancelled: {},
fulfilled: {},
},
},
payment: {
created: {},
status_changed: {
value: {
pending: {},
processed: {},
captured: {},
cancelled: {},
failed: {},
refunded: {},
},
},
invoice_payment_refund_change: {
invoice_payment_refund_created: {},
invoice_payment_refund_status_changed: {
value: {
pending: {},
succeeded: {},
failed: {},
},
},
},
user_interaction: {
status: {
requested: {},
completed: {},
},
},
},
};
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
<v-dialog [progress]="progress()" title="Create shop webhook">
<cc-domain-thrift-editor
[extensions]="extensions$ | async"
[formControl]="control"
namespace="webhooker"
type="WebhookParams"
></cc-domain-thrift-editor>
<div class="flex flex-col gap-4">
<cc-shop-merchant-field [formField]="control.partyShop" />
<v-input-field [formField]="control.url" label="URL" required />
<div class="flex flex-col gap-1.5">
<div class="font-medium text-sm text-neutral-600 dark:text-neutral-300">
Invoice Event Types
</div>
<cc-event-types-field
[formField]="control.eventTypes"
[structure]="eventTypesStructure"
/>
</div>
</div>
<v-dialog-actions>
<button [disabled]="progress() || control.invalid" mat-button (click)="create()">
<button [disabled]="progress() || control().invalid()" mat-button (click)="create()">
Create
</button>
</v-dialog-actions>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,41 @@
import { distinctUntilChanged, map } from 'rxjs';

import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, inject, signal } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { ReactiveFormsModule } from '@angular/forms';
import { FormField, form, required } from '@angular/forms/signals';
import { MatButtonModule } from '@angular/material/button';

import { WebhookParams } from '@vality/domain-proto/webhooker';
import { InvoiceEventType, WebhookParams } from '@vality/domain-proto/webhooker';
import {
DialogModule,
DialogSuperclass,
InputFieldModule,
NotifyLogService,
getValueChanges,
progressTo,
} from '@vality/matez';

import { ThriftShopWebhooksManagementService } from '~/api/services';
import {
DomainMetadataFormExtensionsService,
DomainThriftFormComponent,
} from '~/components/thrift-api-crud';
import { EventTypesFieldComponent } from '~/components/event-types-field';
import { PartyShop, ShopMerchantFieldComponent } from '~/components/shop-merchant-field';

import { SHOP_INVOICE_EVENT_TYPES } from './consts/shop-invoice-event-types';

interface CreateWebhookModel {
partyShop: PartyShop;
url: string;
eventTypes: InvoiceEventType[];
}

@Component({
selector: 'cc-create-webhook-dialog',
imports: [
CommonModule,
DialogModule,
DomainThriftFormComponent,
ReactiveFormsModule,
MatButtonModule,
ShopMerchantFieldComponent,
InputFieldModule,
FormField,
EventTypesFieldComponent,
],
changeDetection: ChangeDetectionStrategy.Eager,
templateUrl: './create-webhook-dialog.component.html',
Expand All @@ -38,26 +46,39 @@ export class CreateWebhookDialogComponent extends DialogSuperclass<
> {
private webhooksManagementService = inject(ThriftShopWebhooksManagementService);
private log = inject(NotifyLogService);
private domainMetadataFormExtensionsService = inject(DomainMetadataFormExtensionsService);

control = new FormControl<Partial<WebhookParams>>(
{ party_ref: { id: this.dialogData.partyId } },
{ nonNullable: true },
);
controlModel = signal<CreateWebhookModel>({
partyShop: {
party_id: this.dialogData.partyId,
shop_id: null,
},
url: '',
eventTypes: [],
});
control = form(this.controlModel, (schemaPath) => {
required(schemaPath.partyShop.party_id);
required(schemaPath.partyShop.shop_id);
required(schemaPath.url);
required(schemaPath.eventTypes);
});
progress = signal(0);
extensions$ = this.domainMetadataFormExtensionsService.createFullDomainObjectsOptionsByType(
'ShopConfigObject',
'shop_config',
getValueChanges(this.control).pipe(
map((value) => value?.party_ref?.id ?? this.dialogData.partyId),
distinctUntilChanged(),
map((partyId) => (obj) => obj.object.shop_config.data.party_ref.id === partyId),
),
);
eventTypesStructure = SHOP_INVOICE_EVENT_TYPES as Record<string, unknown>;

create() {
const { partyShop, url, eventTypes } = this.controlModel();
const params: WebhookParams = {
party_ref: { id: partyShop.party_id },
url,
event_filter: {
invoice: {
shop_ref: { id: partyShop.shop_id },
types: new Set(eventTypes),
},
},
};

this.webhooksManagementService
.Create(this.control.value as WebhookParams)
.Create(params)
.pipe(progressTo(this.progress))
.subscribe(() => {
this.log.success('Webhook created');
Expand Down
56 changes: 56 additions & 0 deletions src/components/event-types-field/event-types-field.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<div class="flex flex-col gap-2 rounded-lg border border-neutral-200 dark:border-neutral-700 p-3">
<div
class="flex items-center justify-between border-b border-neutral-200 dark:border-neutral-700 pb-2"
>
<span class="text-xs text-neutral-500 font-medium">
Selected: {{ selectedLeafIds().size }} / {{ allLeaves().length }}
</span>
<div class="flex gap-1">
<button
[disabled]="disabled()"
class="!text-xs !h-7 !px-2"
mat-button
type="button"
(click)="selectAll()"
>
Select All
</button>
<button
[disabled]="disabled() || selectedLeafIds().size === 0"
class="!text-xs !h-7 !px-2"
mat-button
type="button"
(click)="clearAll()"
>
Clear All
</button>
</div>
</div>

<div class="flex flex-col gap-1 pt-1">
@for (node of tree(); track node.id) {
<ng-container *ngTemplateOutlet="nodeTemplate; context: { node: node }" />
}
</div>
</div>

<ng-template #nodeTemplate let-node="node">
<div class="flex flex-col">
<mat-checkbox
[checked]="isChecked(node)"
[disabled]="disabled()"
[indeterminate]="isIndeterminate(node)"
(change)="toggleNode(node, $event.checked)"
>
{{ node.label }}
</mat-checkbox>

@if (node.children?.length) {
<div class="pl-6 flex flex-col gap-0.5">
@for (child of node.children; track child.id) {
<ng-container *ngTemplateOutlet="nodeTemplate; context: { node: child }" />
}
</div>
}
</div>
</ng-template>
Loading
Loading