Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
fa6d902
Add org-management-proto dependency to package.json and package-lock.…
A77AY Sep 3, 2026
86fb7cc
Add Organization Management service to the API
A77AY Sep 3, 2026
13b99db
feat: implement organization management module and add Thrift service…
A77AY Sep 8, 2026
ba09bdd
chore: update org-management-proto version and bump postcss dependency
A77AY Sep 8, 2026
b4cc60e
refactor: migrate invitations and members lists to pagedObservableRes…
A77AY Sep 8, 2026
e294cad
feat: implement pagination and filtering for organizations list and u…
A77AY Sep 8, 2026
e3d4ff1
refactor: migrate members and invitations components from organizatio…
A77AY Sep 8, 2026
f6ab41f
feat: implement party details view and update navigation routing
A77AY Sep 8, 2026
96ec85a
refactor: update organization and party navigation links to point to …
A77AY Sep 8, 2026
f5044a0
feat: add support for dividers in navigation menus and fix active lin…
A77AY Sep 8, 2026
958eb08
refactor: update organizations filters to use DEFAULT_FILTERS and cou…
A77AY Sep 8, 2026
7e15e0b
feat: add CreateOrganizationDialog component and integrate it into th…
A77AY Sep 9, 2026
11f4603
feat: add organization creation flow and conditional view rendering f…
A77AY Sep 9, 2026
4071480
fix: disable Sentry replays on error sample rate in main.ts
A77AY Sep 9, 2026
7794482
refactor: export ParsedThriftError and switch from Sentry exception c…
A77AY Sep 9, 2026
92ebe44
fix: update withdrawal list after adjustment and make reload options …
A77AY Sep 9, 2026
b43de20
feat: implement Keycloak auto-refresh and session re-login on 401 thr…
A77AY Sep 9, 2026
5632ff7
refactor: update createRequestWachterHeaders to accept optional token…
A77AY Sep 9, 2026
db7d0ff
fix: update keycloak token refresh logic and extend session timeout c…
A77AY Sep 9, 2026
2bf7661
feat: add .agents/ to .gitignore and create skills-lock.json for AI A…
A77AY Sep 9, 2026
5f0fa15
feat: implement organization modification and activation/deactivation…
A77AY Sep 9, 2026
56151cc
feat: update withdrawal dialog subscription to reload on successful r…
A77AY Sep 10, 2026
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,9 @@ authConfig*.json

# Env
.env

# AI Agent Skills (managed via https://skills.sh)
# Note: Officially, committing skills to VCS is recommended (for security and zero-install setup),
# but we intentionally do not commit them to keep the repository clean.
# Track dependencies via `skills-lock.json` and restore via: `npx skills install`
/.agents/
20 changes: 20 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## 1. Thrift API

- Services are registered in `src/api/services.ts` and exported via `injectableServices`.
- Inject `Thrift...Service` directly via `inject(Thrift...Service)`. Do not create intermediate wrapper services.

## 2. UI & Shared Libraries

- Use `@vality/matez` (`projects/matez/`) for shared UI components (`TableModule`, `PageLayoutModule`, `SelectFieldModule`, `DialogModule`, etc.).

## 3. Signal Forms (`@angular/forms/signals`)

- **Custom Controls**: Implement `FormValueControl<T>` with `value = model<T>()` and `control = form(this.value)`. Do not use `FormControlSuperclass` or `ControlValueAccessor`.

## 4. Observable Resource (`@vality/matez`)

- Use `observableResource` or `pagedObservableResource` for async data fetching instead of manual Observable pipelines with `reload$` subjects, `inProgress$`, and `shareReplay`.
- Use `pagedObservableResource` for paginated / list data (with `continuationToken` and `size`), mapping result to `{ result, continuationToken }`.
- Access data and loading state via signals: `resource.value()`, `resource.isLoading()`, and reload using `resource.reload()`.
- For tables, use `<v-table-resource [columns]="columns" [resource]="resource" />`.
- For select fields and other controls, bind signals directly: `[options]="resource.value()"` and `[progress]="resource.isLoading()"`.
49 changes: 44 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@vality/fistful-proto": "^2.0.1-360c737.0",
"@vality/machinegun-proto": "^1.0.1-273f2f3.0",
"@vality/magista-proto": "^2.0.2-bd58cea.0",
"@vality/org-management-proto": "^0.1.1-23aa5d8.0",
"@vality/repairer-proto": "^2.0.2-07b2a51.0",
"@vality/scrooge-proto": "^0.1.1-eec9e06.0",
"@vality/thrift-ts": "^2.5.1-2b658f2.0",
Expand All @@ -51,7 +52,6 @@
"lodash-es": "^4.18.1",
"monaco-editor": "^0.55.1",
"ngx-monaco-editor-v2": "^22.0.4",
"postcss": "^8.5.15",
"rxjs": "~7.8.2",
"short-uuid": "^6.0.3",
"tailwindcss": "^4.2.2",
Expand Down Expand Up @@ -81,7 +81,7 @@
"eslint-plugin-unused-imports": "^4.4.1",
"jsdom": "^29.1.1",
"ng-packagr": "^22.0.0",
"postcss": "^8.5.15",
"postcss": "^8.5.28",
"prettier": "^3.8.4",
"prettier-plugin-organize-attributes": "^1.0.0",
"typescript": "~6.0.3",
Expand Down
28 changes: 18 additions & 10 deletions projects/matez/src/lib/components/nav/nav.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
@for (link of linkGroup.children; track link) {
@let isActive = activeLinks.includes(link);

@if (link.children) {
@if (link.divider) {
<mat-divider></mat-divider>
} @else if (link.children) {
<button
[ngClass]="{
'!text-[var(--mat-sys-on-surface-variant)]': !isActive,
Expand Down Expand Up @@ -39,16 +41,22 @@
</button>
}
@if (isActive && link.children?.length) {
<div class="pl-2 flex flex-col">
<div
class="flex flex-col border-l-1 border-[var(--mat-sys-outline-variant)] ml-6 mb-2"
>
@for (childLink of link.children; track childLink) {
<button
[matButton]="activeLinks.includes(childLink) ? 'tonal' : 'text'"
[routerLink]="childLink.url"
[title]="childLink.label"
class="!justify-start mx-2 !px-4 !text-[var(--mat-sys-on-surface-variant)]"
>
{{ childLink.label }}
</button>
@if (childLink.divider) {
<mat-divider></mat-divider>
} @else {
<button
[matButton]="activeLinks.includes(childLink) ? 'tonal' : 'text'"
[routerLink]="childLink.url"
[title]="childLink.label"
class="!justify-start mx-2 !px-4 !text-[var(--mat-sys-on-surface-variant)]"
>
{{ childLink.label }}
</button>
}
}
</div>
}
Expand Down
4 changes: 3 additions & 1 deletion projects/matez/src/lib/components/nav/nav.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,16 @@ export interface BaseLink {
icon?: string;
children?: BaseLink[];
isHidden?: boolean;
divider?: boolean;
}

export type Link = PossiblyAsyncValue<Overwrite<BaseLink, { children?: Link[] }>, [url: string]>;

function isActiveLink(link: BaseLink, url: string): boolean {
return (
!isNil(url) &&
(url.startsWith(link.url || '') || (!!link.checkUrl && url.startsWith(link.checkUrl || '')))
((Boolean(link.url) && url.startsWith(link.url)) ||
(Boolean(link.checkUrl) && url.startsWith(link.checkUrl)))
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export class TableResourceComponent<T extends object, C extends object> {

update(options: UpdateOptions) {
const res = this.resource();
if ('setOptions' in res) res.setOptions(options);
if (res && 'setOptions' in res) res.setOptions(options);
else if (res && 'reload' in res) res.reload();
}
}
5 changes: 4 additions & 1 deletion projects/matez/src/lib/components/value/value.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@
vHighlight
></span>
} @else if (v.error) {
<mat-icon [matTooltip]="v.error | json">error</mat-icon>
<mat-icon
[matTooltip]="v.error?.['message'] || (v.error | json)"
>error</mat-icon
>
} @else if (emptySymbol) {
<span class="text-[var(--mat-sys-outline)]">{{
emptySymbol
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export class PagedObservableResource<TItem, TParams = void> extends ObservableRe
size,
...options
}: PagedObservableResourceOptions<TItem, TParams>) {
this.pagedOptions = { size: size || 20 };
// TODO: fix types
super.init({
params: EMPTY,
Expand All @@ -89,7 +90,6 @@ export class PagedObservableResource<TItem, TParams = void> extends ObservableRe
},
map: (value) => value.result,
} as never);
this.pagedOptions = { size: size || 20 };
this.hasMore$ = this.accValue$.pipe(map((value) => !!value.continuationToken));
this.hasMore = toSignal(this.hasMore$, { initialValue: false });
this.result$ = this.value$;
Expand Down
11 changes: 11 additions & 0 deletions skills-lock.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": 1,
"skills": {
"angular-developer": {
"source": "angular/skills",
"sourceType": "github",
"skillPath": "angular-developer/SKILL.md",
"computedHash": "0850aa966b6a7da1151daa9d0ef2081bf397f052573a35c1643fb92b948f0f00"
}
}
}
13 changes: 13 additions & 0 deletions src/api/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { metadata$ as fistfulMetadata$ } from '@vality/fistful-proto';
import { metadata$ as machinegunMetadata$ } from '@vality/machinegun-proto';
import { metadata$ as magistaMetadata$ } from '@vality/magista-proto';
import { ThriftAstMetadata, ThriftFormExtension, ThriftViewExtension } from '@vality/ng-thrift';
import { metadata$ as orgManagementMetadata$ } from '@vality/org-management-proto';
import { metadata$ as repairerMetadata$ } from '@vality/repairer-proto';
import { metadata$ as scroogeMetadata$ } from '@vality/scrooge-proto';

Expand Down Expand Up @@ -186,6 +187,17 @@ export const services = [
service: 'Management',
public: 'DestinationManagement',
},

// Organization Management
{
name: Service.OrganizationManagement,
loader: () =>
import('@vality/org-management-proto/admin_management').then((m) => m.AdminManagement),
metadata$: orgManagementMetadata$,
namespace: 'admin_management',
service: 'AdminManagement',
public: 'OrganizationManagement',
},
] as const;

export const { services: injectableServices, provideThriftServices } =
Expand All @@ -210,4 +222,5 @@ export const {
Accounter: ThriftAccountManagementService,
InvoiceTemplating: ThriftInvoiceTemplatingService,
DestinationManagement: ThriftDestinationManagementService,
OrgManager: ThriftOrganizationManagementService,
} = injectableServices;
18 changes: 18 additions & 0 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { LOGGING } from '~/utils';
import { APP_ROUTES } from './app-routes';
import { ROUTING_CONFIG as DEPOSITS_ROUTING_CONFIG } from './deposits/routing-config';
import { ROUTING_CONFIG as MACHINES_ROUTING_CONFIG } from './machines/routing-config';
import { ROUTING_CONFIG as ORGANIZATIONS_ROUTING_CONFIG } from './organizations/routing-config';
import { ROUTING_CONFIG as PAYMENTS_ROUTING_CONFIG } from './payments/routing-config';
import { SHOPS_ROUTING_CONFIG } from './shops';
import { ROUTING_CONFIG as SOURCES_ROUTING_CONFIG } from './sources/routing-config';
Expand Down Expand Up @@ -110,6 +111,7 @@ const createNavLinks = (): Link[] => [
isHidden: isHidden(APP_ROUTES.parties.root.config.services),
children: isPartyPath
? [
{ label: 'Details', url: `${partyPath}/details` },
{
label: 'Shops',
url: `${partyPath}/shops`,
Expand All @@ -118,6 +120,16 @@ const createNavLinks = (): Link[] => [
label: 'Wallets',
url: `${partyPath}/wallets`,
},
{ divider: true },
{
label: 'Members',
url: `${partyPath}/members`,
},
{
label: 'Invitations',
url: `${partyPath}/invitations`,
},
{ divider: true },
{
label: 'Shop webhooks',
url: `${partyPath}/shop-webhooks`,
Expand All @@ -126,6 +138,7 @@ const createNavLinks = (): Link[] => [
label: 'Wallet webhooks',
url: `${partyPath}/wallet-webhooks`,
},
{ divider: true },
{
label: 'Payment RR',
url: `${partyPath}/routing-rules/payment/main`,
Expand All @@ -140,6 +153,11 @@ const createNavLinks = (): Link[] => [
: [],
};
},
{
label: 'Organizations',
url: '/organizations',
isHidden: isHidden(ORGANIZATIONS_ROUTING_CONFIG.services),
},
{
label: 'Shops',
url: '/shops',
Expand Down
14 changes: 13 additions & 1 deletion src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { provideKeycloak } from 'keycloak-angular';
import {
AutoRefreshTokenService,
UserActivityService,
provideKeycloak,
withAutoRefreshToken,
} from 'keycloak-angular';
import { provideMonacoEditor } from 'ngx-monaco-editor-v2';

import { OVERLAY_DEFAULT_CONFIG } from '@angular/cdk/overlay';
Expand Down Expand Up @@ -55,6 +60,13 @@ export const appConfig: ApplicationConfig = {
provideKeycloak({
config: './assets/authConfig.json' as never,
initOptions: { onLoad: 'login-required', checkLoginIframe: !isDevMode() },
features: [
withAutoRefreshToken({
onInactivityTimeout: 'login',
sessionTimeout: 4 * 60 * 60_000,
}),
],
providers: [AutoRefreshTokenService, UserActivityService],
}),
provideHttpClient(withXhr(), withInterceptorsFromDi()),
{ provide: MAT_DATE_FORMATS, useValue: DEFAULT_MAT_DATE_FORMATS },
Expand Down
4 changes: 4 additions & 0 deletions src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ export const routes: Routes = [
path: 'studio',
loadComponent: () => import('./studio').then((m) => m.StudioComponent),
},
{
path: 'organizations',
loadChildren: () => import('./organizations').then((m) => m.ORGANIZATIONS_ROUTES),
},
{
path: '404',
loadChildren: () => import('./not-found').then((m) => m.NotFoundModule),
Expand Down
Loading
Loading