chore(integration): revamp Zapier and MakeCom plugin integration (#9635)

* fix(zapier): enhance authorization flow for between Gauzy and Zapier platform

* chore: upgraded zapier app version, refactor dependecies and app oauth

* fix: enhance security by storing sensive credentials on server-side and improve UI

* fix: update zapier model interfaces

* fix: update zapier translation and model interface

* fix: refactor make settings by avoiding exposing sensitive credentials

* chore: add Make API resources types

* feat: resolve post-install redirect url and add methods to interact with Make settings

* chore: update texte translations for new added texte in all language translations

* feat: add and refactor UI components for the Make settings and interactions

* fix: fix typos spelling

* fix: add review suggestions

* fix: add review suggestions

* fix: fix review suggestions from AI bots

* fix: add review suggestions from AI bots

* fix: add review comment suggestions from bots

* fix: fix Deepscan error

* fix: add review suggestions

* feat: add settings for zaps and zap-template with texte translations

* fix: review suggestions

* fix: extend OAuth scopes
This commit is contained in:
Roland MN
2026-04-06 15:27:10 +02:00
committed by GitHub
parent 4635c7e939
commit 1bcee94793
74 changed files with 7039 additions and 1271 deletions
+4
View File
@@ -329,6 +329,9 @@
"iquery",
"iqueue",
"iremote",
"isinvalid",
"islinked",
"islocked",
"ISMTP",
"isready",
"isready",
@@ -700,6 +703,7 @@
"sslmode",
"SSSSZ",
"stackoverflow",
"suberrors",
"subquery",
"stagecivo",
"stagecw",
+94 -4
View File
@@ -44,12 +44,102 @@ export interface IMakeComAuthConfig {
postInstallUrl: string;
}
export interface IMakeComCreateIntegration extends IBasePerTenantAndOrganizationEntityModel {
clientId: string;
clientSecret: string;
}
export interface IMakeComCreateIntegration extends IBasePerTenantAndOrganizationEntityModel {}
export interface IMakeComOAuthCodeExchange {
code: string;
state: string; // CSRF protection
}
// Make.com API Resource Types
export type MakeComZone = 'eu1' | 'eu2' | 'us1' | 'us2';
export const MAKE_COM_ZONES: MakeComZone[] = ['eu1', 'eu2', 'us1', 'us2'];
export interface IMakeComOrganization {
id: number;
name: string;
countryId?: number;
timezoneId?: number;
license?: string;
zone?: MakeComZone;
}
export interface IMakeComTeam {
id: number;
name: string;
organizationId: number;
}
export interface IMakeComConnection {
id: number;
name: string;
accountName?: string;
accountLabel?: string;
accountType?: string;
packageName?: string;
expire?: string;
metadata?: Record<string, unknown>;
teamId?: number;
upgradeable?: boolean;
scoped?: boolean;
editable?: boolean;
}
export interface IMakeComScenario {
id: number;
name: string;
teamId: number;
hookId?: number;
description?: string;
folderId?: number;
isEnabled?: boolean;
isPaused?: boolean;
usedPackages?: string[];
lastEdit?: string;
scheduling?: IMakeComScenarioScheduling;
islinked?: boolean;
isinvalid?: boolean;
islocked?: boolean;
createdByUser?: { id: number; name: string; email: string };
updatedByUser?: { id: number; name: string; email: string };
}
export interface IMakeComScenarioScheduling {
type: string;
interval?: number;
}
export interface IMakeComHook {
id: number;
name: string;
teamId: number;
url?: string;
type?: string;
typeName?: string;
packageName?: string;
theme?: string;
enabled?: boolean;
data?: Record<string, unknown>;
queueCount?: number;
queueLimit?: number;
}
export interface IMakeComTemplate {
id: number;
name: string;
description?: string;
usedPackages?: string[];
public?: boolean;
publishedDate?: string;
teamId?: number;
}
export interface IMakeComSetupStatus {
hasAccessToken: boolean;
zone: MakeComZone | null;
makeOrganizationId: number | null;
makeTeamId: number | null;
isComplete: boolean;
}
+65 -2
View File
@@ -62,9 +62,72 @@ export interface IZapierEndpoint {
slug: string;
}
/**
* Represents a single step in a Zapier Zap (trigger or action).
* See https://docs.zapier.com/powered-by-zapier/api-reference/zaps/create-a-zap
*/
export interface IZapierZapStep {
/** Fully-qualified action identifier, e.g. "example_core:Vn7xbE60" */
action: string;
/** Authentication ID to use for this step, or null */
authentication?: string | null;
/** Input field values for this step */
inputs?: Record<string, any> | null;
/** Optional user-facing title for this step */
title?: string | null;
}
/**
* Links returned alongside a Zap, such as a direct link to the Zap editor.
*/
export interface IZapierZapLinks {
html_editor?: string;
}
/**
* Represents a Zapier Zap.
* See https://docs.zapier.com/powered-by-zapier/api-reference/zaps/get-zaps-[v2]
* and https://docs.zapier.com/powered-by-zapier/api-reference/zaps/create-a-zap
*/
export interface IZapierZap {
type?: 'zap';
id: string;
title: string;
is_enabled?: boolean;
last_successful_run_date?: string | null;
updated_at?: string;
links?: IZapierZapLinks;
steps?: IZapierZapStep[];
// Fields returned by the list endpoint (GET /v2/zaps)
state?: string;
url?: string;
create_time?: string;
modified_time?: string;
}
/**
* Input for creating a new Zap via the Zapier Partner API.
* See https://docs.zapier.com/powered-by-zapier/api-reference/zaps/create-a-zap
*/
export interface IZapierCreateZapInput {
title: string;
steps: IZapierZapStep[];
}
/**
* Represents a Zapier Zap template.
* See https://docs.zapier.com/powered-by-zapier/api-reference/zap-templates/get-zap-templates
*/
export interface IZapierZapTemplate {
id: string;
title: string;
description?: string;
url?: string;
create_url?: string;
}
export interface ICreateZapierIntegrationInput extends IBasePerTenantAndOrganizationEntityModel {
client_id: string;
client_secret: string;
code?: string;
redirect_uri?: string;
}
@@ -1,6 +1,10 @@
export * from './lib/components/make-com-authorize/make-com-authorize.component';
export * from './lib/components/make-com-callback/make-com-callback.component';
export * from './lib/components/make-com-settings/make-com-settings.component';
export * from './lib/components/make-com-scenarios/make-com-scenarios.component';
export * from './lib/components/make-com-hooks/make-com-hooks.component';
export * from './lib/components/make-com-connections/make-com-connections.component';
export * from './lib/components/make-com-templates/make-com-templates.component';
export * from './lib/components/make/make.component';
export * from './lib/integration-make-com-ui.module';
export * from './lib/integration-make-com.layout.component';
@@ -4,52 +4,24 @@
<h5>{{ 'MENU.MAKE_COM' | translate }}</h5>
</nb-card-header>
<nb-card-body>
<form class="col-xl-6 col-12" [formGroup]="form" (ngSubmit)="startAuthorization()">
<div class="form-group">
<label for="clientId" class="label">{{ 'INTEGRATIONS.MAKE_COM_PAGE.CLIENT_ID' | translate }}</label>
<input
fullWidth
id="clientId"
formControlName="clientId"
type="text"
nbInput
[placeholder]="'FORM.PLACEHOLDERS.MAKE_COM_CLIENT_ID' | translate"
/>
@if (form.get('clientId')?.touched && form.get('clientId')?.errors?.required) {
<div
class="text-danger"
>
{{ 'VALIDATION.FIELD_REQUIRED' | translate }}
</div>
}
</div>
<div class="form-group">
<label for="clientSecret" class="label">{{
'INTEGRATIONS.MAKE_COM_PAGE.CLIENT_SECRET' | translate
}}</label>
<input
fullWidth
id="clientSecret"
formControlName="clientSecret"
type="password"
nbInput
[placeholder]="'FORM.PLACEHOLDERS.MAKE_COM_CLIENT_SECRET' | translate"
/>
@if (form.get('clientSecret')?.touched && form.get('clientSecret')?.errors?.required) {
<div
class="text-danger"
>
{{ 'VALIDATION.FIELD_REQUIRED' | translate }}
</div>
}
</div>
<div class="hint">
<nb-icon icon="info-outline"></nb-icon>
{{ 'INTEGRATIONS.MAKE_COM_PAGE.NEXT_STEP_INFO' | translate }}
</div>
<button nbButton status="primary" [disabled]="form.invalid" size="small" outline type="submit">
{{ 'BUTTONS.NEXT' | translate }}
</button>
</form>
</nb-card-body>
</nb-card>
<div class="col-xl-6 col-12">
<div class="hint mb-4">
<nb-icon icon="info-outline"></nb-icon>
{{ 'INTEGRATIONS.MAKE_COM_PAGE.CONNECT_INFO' | translate }}
</div>
<button
type="button"
nbButton
status="primary"
size="small"
[disabled]="loading"
(click)="connectToMakeCom()"
>
@if (loading) {
<nb-icon icon="loader-outline" class="spin"></nb-icon>
}
{{ 'INTEGRATIONS.MAKE_COM_PAGE.CONNECT_TO_MAKE_COM' | translate }}
</button>
</div>
</nb-card-body>
</nb-card>
@@ -1,12 +1,11 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { tap, catchError, filter } from 'rxjs/operators';
import { EMPTY } from 'rxjs';
import { MakeComService, ToastrService, Store } from '@gauzy/ui-core/core';
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
import { TranslateService } from '@ngx-translate/core';
import { IMakeComCreateIntegration, IOrganization, IIntegrationTenant, IntegrationEnum } from '@gauzy/contracts';
import { IOrganization, IIntegrationTenant, IntegrationEnum } from '@gauzy/contracts';
import { Router, ActivatedRoute } from '@angular/router';
import { IntegrationsService } from '@gauzy/ui-core/core';
@@ -18,15 +17,12 @@ import { IntegrationsService } from '@gauzy/ui-core/core';
standalone: false
})
export class AuthorizationComponent extends TranslationBaseComponent implements OnInit {
public form: FormGroup;
public loading = false;
public oauthConfig: { clientId: string; redirectUri: string } = null;
public organization: IOrganization;
public rememberState: boolean;
private static readonly MAKE_DASHBOARD_ROUTE = '/pages/integrations/makecom';
constructor(
private readonly _fb: FormBuilder,
private readonly _makeComService: MakeComService,
private readonly _toastrService: ToastrService,
public readonly translateService: TranslateService,
@@ -39,16 +35,7 @@ export class AuthorizationComponent extends TranslationBaseComponent implements
}
ngOnInit() {
this._initializeForm();
this._loadOrganization();
this._loadOAuthConfig();
}
private _initializeForm() {
this.form = this._fb.group({
clientId: ['', [Validators.required]],
clientSecret: ['', [Validators.required]]
});
}
private _loadOrganization() {
@@ -62,25 +49,6 @@ export class AuthorizationComponent extends TranslationBaseComponent implements
.subscribe();
}
private _loadOAuthConfig() {
this.loading = true;
this._makeComService
.getOAuthConfig()
.pipe(
tap((config) => {
this.oauthConfig = config;
}),
catchError((error) => {
console.error('Error loading OAuth config:', error);
return EMPTY;
}),
untilDestroyed(this)
)
.subscribe(() => {
this.loading = false;
});
}
/**
* Check if there's an existing Make.com integration
*/
@@ -119,22 +87,22 @@ export class AuthorizationComponent extends TranslationBaseComponent implements
}
/**
* Start OAuth authorization flow
* Start OAuth authorization flow.
* No credentials needed — server uses its own env-configured client_id/secret.
*/
startAuthorization() {
if (this.form.invalid || !this.organization) {
connectToMakeCom() {
if (!this.organization) {
this._toastrService.error(
this.getTranslation('INTEGRATIONS.MAKE_PAGE.ERRORS.INVALID_FORM'),
this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.NO_ORGANIZATION'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return;
}
this.loading = true;
const credentials: IMakeComCreateIntegration = this.form.value;
this._makeComService
.addOAuthSettings(credentials)
.initializeIntegration({ organizationId: this.organization.id })
.pipe(
tap((response) => {
// Redirect to Make.com authorization page
@@ -146,13 +114,14 @@ export class AuthorizationComponent extends TranslationBaseComponent implements
this.getTranslation('TOASTR.TITLE.ERROR')
);
console.error('Error starting authorization:', error);
this.loading = false;
return EMPTY;
}),
untilDestroyed(this)
)
.subscribe(() => {
this.loading = false;
});
this.loading = false;
});
}
/**
@@ -0,0 +1,100 @@
<div class="connections-container">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6>{{ 'INTEGRATIONS.MAKE_COM_PAGE.CONNECTIONS' | translate }}</h6>
<button
type="button"
nbButton
size="small"
status="basic"
(click)="refresh()"
[disabled]="loading()"
[attr.aria-label]="'INTEGRATIONS.MAKE_COM_PAGE.REFRESH_CONNECTIONS' | translate"
>
@if (loading()) {
<nb-icon icon="loader-outline" class="spin"></nb-icon>
} @else {
<nb-icon icon="refresh-outline"></nb-icon>
}
</button>
</div>
@if (loading()) {
<div class="d-flex justify-content-center py-5">
<nb-spinner></nb-spinner>
</div>
}
<!-- Setup incomplete -->
@if (!loading() && setupStatus() && !setupStatus()!.isComplete) {
<nb-card accent="warning">
<nb-card-body class="text-center py-4">
<nb-icon icon="alert-triangle-outline" class="empty-icon mb-2" status="warning"></nb-icon>
@if (!setupStatus()!.hasAccessToken) {
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.AUTHORIZE' | translate }}</p>
} @else if (!setupStatus()!.zone) {
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.ZONE' | translate }}</p>
} @else if (!setupStatus()!.makeOrganizationId) {
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.ORGANIZATION' | translate }}</p>
} @else if (!setupStatus()!.makeTeamId) {
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.TEAM' | translate }}</p>
}
</nb-card-body>
</nb-card>
}
@if (!loading() && setupStatus()?.isComplete && connections().length === 0) {
<nb-card accent="info">
<nb-card-body class="text-center py-4">
<nb-icon icon="swap-outline" class="empty-icon mb-2"></nb-icon>
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.NO_CONNECTIONS' | translate }}</p>
</nb-card-body>
</nb-card>
}
@if (!loading() && setupStatus()?.isComplete && connections().length > 0) {
<div class="connection-list">
@for (conn of connections(); track conn.id) {
<nb-card class="connection-card mb-2">
<nb-card-body class="d-flex align-items-center justify-content-between">
<div class="connection-info">
<div class="d-flex align-items-center gap-2">
<nb-icon icon="swap-outline" status="primary"></nb-icon>
<span class="connection-name">{{ conn.name }}</span>
</div>
@if (conn.accountName) {
<small class="text-hint d-block mt-1">{{ 'INTEGRATIONS.MAKE_COM_PAGE.CONNECTION.ACCOUNT' | translate: { value: conn.accountName } }}</small>
}
@if (conn.accountType) {
<small class="text-hint d-block">{{ 'INTEGRATIONS.MAKE_COM_PAGE.CONNECTION.TYPE' | translate: { value: conn.accountType } }}</small>
}
@if (conn.packageName) {
<small class="text-hint d-block">{{ 'INTEGRATIONS.MAKE_COM_PAGE.CONNECTION.PACKAGE' | translate: { value: conn.packageName } }}</small>
}
@if (conn.expire) {
<small class="text-hint d-block">{{ 'INTEGRATIONS.MAKE_COM_PAGE.CONNECTION.EXPIRES' | translate: { value: (conn.expire | date: 'medium') } }}</small>
}
</div>
<div class="connection-actions">
<button
type="button"
nbButton
size="small"
status="info"
[nbTooltip]="'INTEGRATIONS.MAKE_COM_PAGE.TEST_CONNECTION' | translate"
(click)="testConnection(conn)"
[disabled]="actionLoading()[conn.id]"
[attr.aria-label]="'INTEGRATIONS.MAKE_COM_PAGE.TEST_CONNECTION' | translate"
>
@if (actionLoading()[conn.id]) {
<nb-icon icon="loader-outline" class="spin"></nb-icon>
} @else {
<nb-icon icon="checkmark-circle-outline"></nb-icon>
}
</button>
</div>
</nb-card-body>
</nb-card>
}
</div>
}
</div>
@@ -0,0 +1,34 @@
.connections-container {
padding: 0.5rem 0;
}
.connection-card {
margin-bottom: 0.5rem;
nb-card-body {
padding: 1rem 1rem;
}
}
.connection-name {
font-weight: 600;
font-size: 0.95rem;
}
.empty-icon {
font-size: 2rem;
color: var(--gauzy-text-color-2);
}
.gap-2 {
gap: 0.5rem;
}
.spin {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@@ -0,0 +1,122 @@
import { Component, OnInit, ChangeDetectionStrategy, inject, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { tap, catchError, finalize } from 'rxjs/operators';
import { EMPTY } from 'rxjs';
import { TranslateModule } from '@ngx-translate/core';
import { NbButtonModule, NbCardModule, NbIconModule, NbSpinnerModule, NbTooltipModule } from '@nebular/theme';
import { IMakeComConnection, IMakeComSetupStatus } from '@gauzy/contracts';
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
import { MakeComStoreService, ToastrService } from '@gauzy/ui-core/core';
@UntilDestroy({ checkProperties: true })
@Component({
selector: 'ngx-make-com-connections',
templateUrl: './make-com-connections.component.html',
styleUrls: ['./make-com-connections.component.scss'],
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
CommonModule,
TranslateModule,
NbButtonModule,
NbCardModule,
NbIconModule,
NbSpinnerModule,
NbTooltipModule
]
})
export class MakeComConnectionsComponent extends TranslationBaseComponent implements OnInit {
private readonly _makeComStoreService = inject(MakeComStoreService);
private readonly _toastrService = inject(ToastrService);
public connections = signal<IMakeComConnection[]>([]);
public loading = signal(false);
public actionLoading = signal<Record<number, boolean>>({});
public setupStatus = signal<IMakeComSetupStatus | null>(null);
constructor() {
super();
}
ngOnInit() {
this._checkSetupAndLoad();
}
private _checkSetupAndLoad() {
this.loading.set(true);
this._makeComStoreService
.loadSetupStatus()
.pipe(
tap((status) => {
this.setupStatus.set(status);
if (status.isComplete) {
this._loadConnections();
} else {
this.connections.set([]);
this.loading.set(false);
}
}),
catchError((error) => {
this.setupStatus.set(null);
this.connections.set([]);
this.loading.set(false);
this._toastrService.error(
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_SETUP_STATUS'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return EMPTY;
}),
untilDestroyed(this)
)
.subscribe();
}
private _loadConnections() {
this.loading.set(true);
this._makeComStoreService
.loadConnections()
.pipe(
tap((connections) => this.connections.set(connections)),
catchError((error) => {
this.connections.set([]);
this._toastrService.error(
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_CONNECTIONS'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return EMPTY;
}),
finalize(() => this.loading.set(false)),
untilDestroyed(this)
)
.subscribe();
}
testConnection(connection: IMakeComConnection) {
this.actionLoading.update((prev) => ({ ...prev, [connection.id]: true }));
this._makeComStoreService
.testConnection(connection.id)
.pipe(
tap(() => {
this._toastrService.success(
this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.CONNECTION_VALID', { name: connection.name }),
this.getTranslation('TOASTR.TITLE.SUCCESS')
);
}),
catchError((error) => {
this._toastrService.error(
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.TEST_CONNECTION'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return EMPTY;
}),
finalize(() => this.actionLoading.update((prev) => ({ ...prev, [connection.id]: false }))),
untilDestroyed(this)
)
.subscribe();
}
refresh() {
this._checkSetupAndLoad();
}
}
@@ -0,0 +1,118 @@
<div class="hooks-container">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6>{{ 'INTEGRATIONS.MAKE_COM_PAGE.WEBHOOKS' | translate }}</h6>
<button
type="button"
nbButton
size="small"
status="basic"
(click)="refresh()"
[disabled]="loading"
[attr.aria-label]="'INTEGRATIONS.MAKE_COM_PAGE.WEBHOOKS_REFRESH' | translate"
>
<nb-icon icon="refresh-outline"></nb-icon>
</button>
</div>
@if (loading) {
<div class="d-flex justify-content-center py-5">
<nb-spinner></nb-spinner>
</div>
}
<!-- Setup incomplete -->
@if (!loading && setupStatus && !setupStatus.isComplete) {
<nb-card accent="warning">
<nb-card-body class="text-center py-4">
<nb-icon icon="alert-triangle-outline" class="empty-icon mb-2" status="warning"></nb-icon>
@if (!setupStatus.hasAccessToken) {
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.AUTHORIZE' | translate }}</p>
} @else if (!setupStatus.zone) {
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.ZONE' | translate }}</p>
} @else if (!setupStatus.makeOrganizationId) {
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.ORGANIZATION' | translate }}</p>
} @else if (!setupStatus.makeTeamId) {
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.TEAM' | translate }}</p>
}
</nb-card-body>
</nb-card>
}
@if (!loading && setupStatus?.isComplete && hooks.length === 0) {
<nb-card accent="info">
<nb-card-body class="text-center py-4">
<nb-icon icon="link-2-outline" class="empty-icon mb-2"></nb-icon>
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.NO_HOOKS' | translate }}</p>
</nb-card-body>
</nb-card>
}
@if (!loading && setupStatus?.isComplete && hooks.length > 0) {
<div class="hook-list">
@for (hook of hooks; track hook.id) {
<nb-card class="hook-card mb-2">
<nb-card-body class="d-flex align-items-center justify-content-between">
<div class="hook-info">
<div class="d-flex align-items-center gap-2">
<nb-icon icon="link-2-outline" [status]="hook.enabled ? 'success' : 'basic'"></nb-icon>
<span class="hook-name">{{ hook.name }}</span>
@if (hook.enabled) {
<span class="badge badge-success">{{ 'INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_ENABLED' | translate }}</span>
} @else {
<span class="badge badge-basic">{{ 'INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_DISABLED' | translate }}</span>
}
</div>
@if (hook.typeName) {
<small class="text-hint d-block mt-1">{{ 'INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_TYPE' | translate }}: {{ hook.typeName }}</small>
}
@if (hook.url) {
<small class="text-hint d-block hook-url">{{ hook.url }}</small>
}
@if (hook.queueCount != null) {
<small class="text-hint d-block">
{{ 'INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_QUEUE' | translate }}: {{ hook.queueCount }}@if (hook.queueLimit != null) { / {{ hook.queueLimit }}}
</small>
}
</div>
<div class="hook-actions d-flex gap-2">
<button
type="button"
nbButton
size="small"
[status]="hook.enabled ? 'warning' : 'success'"
[nbTooltip]="hook.enabled ? ('INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_DISABLE' | translate) : ('INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_ENABLE' | translate)"
(click)="toggleHook(hook)"
[disabled]="toggleLoading[hook.id] || pingLoading[hook.id]"
[attr.aria-label]="hook.enabled
? ('INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_DISABLE' | translate)
: ('INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_ENABLE' | translate)"
>
@if (toggleLoading[hook.id]) {
<nb-icon icon="loader-outline" class="spin"></nb-icon>
} @else {
<nb-icon [icon]="hook.enabled ? 'toggle-right-outline' : 'toggle-left-outline'"></nb-icon>
}
</button>
<button
type="button"
nbButton
size="small"
status="info"
[nbTooltip]="'INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_PING' | translate"
(click)="pingHook(hook)"
[disabled]="toggleLoading[hook.id] || pingLoading[hook.id]"
[attr.aria-label]="'INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_PING' | translate"
>
@if (pingLoading[hook.id]) {
<nb-icon icon="loader-outline" class="spin"></nb-icon>
} @else {
<nb-icon icon="radio-outline"></nb-icon>
}
</button>
</div>
</nb-card-body>
</nb-card>
}
</div>
}
</div>
@@ -0,0 +1,58 @@
.hooks-container {
padding: 0.5rem 0;
}
.hook-card {
margin-bottom: 0.5rem;
nb-card-body {
padding: 1rem 1.25rem;
}
}
.hook-name {
font-weight: 600;
font-size: 0.95rem;
}
.hook-url {
font-family: monospace;
font-size: 0.75rem;
word-break: break-all;
max-width: 500px;
}
.badge {
font-size: 0.7rem;
padding: 0.15rem 0.5rem;
border-radius: 4px;
font-weight: 500;
&-success {
background-color: var(--color-success-transparent-default);
color: var(--color-success-default);
}
&-basic {
background-color: var(--color-basic-transparent-default);
color: var(--color-basic-600);
}
}
.empty-icon {
font-size: 2rem;
color: var(--color-basic-600);
}
.gap-2 {
gap: 0.5rem;
}
.spin {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@@ -0,0 +1,159 @@
import { Component, OnInit, inject } from '@angular/core';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { tap, catchError, finalize, switchMap, timeout } from 'rxjs/operators';
import { EMPTY, TimeoutError } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import { IMakeComHook, IMakeComSetupStatus } from '@gauzy/contracts';
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
import { MakeComStoreService, ToastrService } from '@gauzy/ui-core/core';
@UntilDestroy({ checkProperties: true })
@Component({
selector: 'ngx-make-com-hooks',
templateUrl: './make-com-hooks.component.html',
styleUrls: ['./make-com-hooks.component.scss'],
standalone: false
})
export class MakeComHooksComponent extends TranslationBaseComponent implements OnInit {
public hooks: IMakeComHook[] = [];
public loading = false;
public toggleLoading: Record<number, boolean> = {};
public pingLoading: Record<number, boolean> = {};
public setupStatus: IMakeComSetupStatus | null = null;
private readonly _makeComStoreService = inject(MakeComStoreService);
private readonly _toastrService = inject(ToastrService);
constructor(public readonly translateService: TranslateService) {
super(translateService);
}
ngOnInit() {
this._checkSetupAndLoad();
}
private _checkSetupAndLoad() {
this.loading = true;
this.hooks = [];
this._makeComStoreService
.loadSetupStatus()
.pipe(
timeout(10_000),
tap((status) => {
this.setupStatus = status;
if (status.isComplete) {
this._loadHooks();
} else {
this.hooks = [];
this.loading = false;
}
}),
catchError((error) => {
this.setupStatus = null;
this.loading = false;
const message =
error instanceof TimeoutError
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.REQUEST_TIMEOUT')
: error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_SETUP_STATUS');
this._toastrService.error(message, this.getTranslation('TOASTR.TITLE.ERROR'));
return EMPTY;
}),
untilDestroyed(this)
)
.subscribe();
}
private _loadHooks() {
this.loading = true;
this._makeComStoreService
.loadHooks()
.pipe(
timeout(15_000),
tap((hooks) => (this.hooks = hooks)),
catchError((error) => {
this.hooks = [];
this._toastrService.error(
error instanceof TimeoutError
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.REQUEST_TIMEOUT')
: error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_WEBHOOKS'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return EMPTY;
}),
finalize(() => (this.loading = false)),
untilDestroyed(this)
)
.subscribe();
}
toggleHook(hook: IMakeComHook) {
this.toggleLoading[hook.id] = true;
const action$ = hook.enabled
? this._makeComStoreService.disableHook(hook.id)
: this._makeComStoreService.enableHook(hook.id);
action$
.pipe(
catchError((error) => {
this._toastrService.error(
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.TOGGLE_WEBHOOK'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return EMPTY;
}),
switchMap(() => this._makeComStoreService.loadHooks()),
catchError((error) => {
// Toggle succeeded but refresh failed — update the local hook state
const target = this.hooks.find((h) => h.id === hook.id);
if (target) {
target.enabled = !hook.enabled;
}
this._toastrService.error(
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_WEBHOOKS'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return EMPTY;
}),
tap((hooks) => {
this.hooks = hooks;
this._toastrService.success(
hook.enabled
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.WEBHOOK_TOGGLED_OFF')
: this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.WEBHOOK_TOGGLED_ON'),
this.getTranslation('TOASTR.TITLE.SUCCESS')
);
}),
finalize(() => (this.toggleLoading[hook.id] = false)),
untilDestroyed(this)
)
.subscribe();
}
pingHook(hook: IMakeComHook) {
this.pingLoading[hook.id] = true;
this._makeComStoreService
.pingHook(hook.id)
.pipe(
tap(() => {
this._toastrService.success(
this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.WEBHOOK_PINGED', { name: hook.name }),
this.getTranslation('TOASTR.TITLE.SUCCESS')
);
}),
catchError((error) => {
this._toastrService.error(
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.PING_WEBHOOK'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return EMPTY;
}),
finalize(() => (this.pingLoading[hook.id] = false)),
untilDestroyed(this)
)
.subscribe();
}
refresh() {
this._checkSetupAndLoad();
}
}
@@ -0,0 +1,121 @@
<div class="scenarios-container">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6>{{ 'INTEGRATIONS.MAKE_COM_PAGE.SCENARIOS' | translate }}</h6>
<button
type="button"
nbButton
size="small"
status="basic"
(click)="refresh()"
[disabled]="loading()"
[attr.aria-label]="'BUTTONS.REFRESH' | translate"
>
<nb-icon icon="refresh-outline"></nb-icon>
</button>
</div>
@if (loading()) {
<div class="d-flex justify-content-center py-5">
<nb-spinner></nb-spinner>
</div>
}
<!-- Setup incomplete -->
@if (!loading() && setupStatus() && !setupStatus()!.isComplete) {
<nb-card accent="warning">
<nb-card-body class="text-center py-4">
<nb-icon icon="alert-triangle-outline" class="empty-icon mb-2" status="warning"></nb-icon>
@if (setupMessageKey()) {
<p>{{ setupMessageKey() | translate }}</p>
}
</nb-card-body>
</nb-card>
}
<!-- Empty state -->
@if (!loading() && setupStatus()?.isComplete && scenarios().length === 0) {
<nb-card accent="info">
<nb-card-body class="text-center py-4">
<nb-icon icon="flash-off-outline" class="empty-icon mb-2"></nb-icon>
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.NO_SCENARIOS' | translate }}</p>
</nb-card-body>
</nb-card>
}
<!-- Scenario list -->
@if (!loading() && setupStatus()?.isComplete && scenarios().length > 0) {
<div class="scenario-list">
@for (scenario of scenarios(); track scenario.id) {
<nb-card class="scenario-card mb-2">
<nb-card-body class="d-flex align-items-center justify-content-between">
<div class="scenario-info">
<div class="d-flex align-items-center gap-2">
<nb-icon icon="flash-outline" [status]="scenario.isEnabled ? 'success' : 'basic'"></nb-icon>
<span class="scenario-name">{{ scenario.name }}</span>
@if (scenario.isEnabled) {
<span class="badge badge-success">
{{ 'INTEGRATIONS.MAKE_COM_PAGE.SCENARIO_ACTIVE' | translate }}
</span>
} @else {
<span class="badge badge-basic">
{{ 'INTEGRATIONS.MAKE_COM_PAGE.SCENARIO_INACTIVE' | translate }}
</span>
}
</div>
@if (scenario.description) {
<small class="text-hint d-block mt-1">{{ scenario.description }}</small>
}
@if (scenario.lastEdit) {
<small class="text-hint d-block">
{{
'INTEGRATIONS.MAKE_COM_PAGE.SCENARIO_LAST_EDITED'
| translate: { value: (scenario.lastEdit | date: 'medium') }
}}
</small>
}
@if (scenario.usedPackages?.length) {
<div class="packages mt-1">
@for (pkg of scenario.usedPackages; track pkg) {
<span class="package-tag">{{ pkg }}</span>
}
</div>
}
</div>
<div class="scenario-actions d-flex gap-2">
<button
type="button"
nbButton
size="small"
[status]="scenario.isEnabled ? 'warning' : 'success'"
[nbTooltip]="scenario.isEnabled ? ('INTEGRATIONS.MAKE_COM_PAGE.SCENARIO_DISABLE' | translate) : ('INTEGRATIONS.MAKE_COM_PAGE.SCENARIO_ENABLE' | translate)"
(click)="toggleScenario(scenario)"
[disabled]="actionLoading()[scenario.id]"
[attr.aria-label]="scenario.isEnabled
? ('INTEGRATIONS.MAKE_COM_PAGE.SCENARIO_DISABLE' | translate)
: ('INTEGRATIONS.MAKE_COM_PAGE.SCENARIO_ENABLE' | translate)"
>
@if (actionLoading()[scenario.id]) {
<nb-icon icon="loader-outline" class="spin"></nb-icon>
} @else {
<nb-icon [icon]="scenario.isEnabled ? 'pause-circle-outline' : 'play-circle-outline'"></nb-icon>
}
</button>
<button
type="button"
nbButton
size="small"
status="info"
[nbTooltip]="'INTEGRATIONS.MAKE_COM_PAGE.SCENARIO_RUN_ONCE' | translate"
(click)="runScenario(scenario)"
[disabled]="actionLoading()[scenario.id]"
[attr.aria-label]="'INTEGRATIONS.MAKE_COM_PAGE.SCENARIO_RUN_ONCE' | translate"
>
<nb-icon icon="arrow-right-outline"></nb-icon>
</button>
</div>
</nb-card-body>
</nb-card>
}
</div>
}
</div>
@@ -0,0 +1,61 @@
.scenarios-container {
padding: 0.5rem 0;
}
.scenario-card {
margin-bottom: 0.5rem;
nb-card-body {
padding: 1rem 1.25rem;
}
}
.scenario-name {
font-weight: 600;
font-size: 0.95rem;
}
.badge {
font-size: 0.7rem;
padding: 0.15rem 0.5rem;
border-radius: 4px;
font-weight: 500;
&-success {
background-color: var(--color-success-transparent-default);
color: var(--color-success-default);
}
&-basic {
background-color: var(--color-basic-transparent-default);
color: var(--color-basic-600);
}
}
.package-tag {
display: inline-block;
font-size: 0.7rem;
padding: 0.1rem 0.4rem;
margin-right: 0.25rem;
border-radius: 3px;
background-color: var(--color-basic-transparent-default);
color: var(--color-basic-600);
}
.empty-icon {
font-size: 2rem;
color: var(--color-basic-600);
}
.gap-2 {
gap: 0.5rem;
}
.spin {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@@ -0,0 +1,167 @@
import { Component, OnInit, ChangeDetectionStrategy, computed, inject, signal } from '@angular/core';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { tap, catchError, finalize, switchMap } from 'rxjs/operators';
import { EMPTY } from 'rxjs';
import { IMakeComScenario, IMakeComSetupStatus } from '@gauzy/contracts';
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
import { MakeComStoreService, ToastrService } from '@gauzy/ui-core/core';
@UntilDestroy({ checkProperties: true })
@Component({
selector: 'ngx-make-com-scenarios',
templateUrl: './make-com-scenarios.component.html',
styleUrls: ['./make-com-scenarios.component.scss'],
standalone: false,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class MakeComScenariosComponent extends TranslationBaseComponent implements OnInit {
private readonly _makeComStoreService = inject(MakeComStoreService);
private readonly _toastrService = inject(ToastrService);
public scenarios = signal<IMakeComScenario[]>([]);
public loading = signal(false);
public actionLoading = signal<Record<number, boolean>>({});
public setupStatus = signal<IMakeComSetupStatus | null>(null);
public setupMessageKey = computed<string | null>(() => {
const status = this.setupStatus();
if (!status || status.isComplete) {
return null;
}
if (!status.hasAccessToken) {
return 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.AUTHORIZE';
}
if (!status.zone) {
return 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.ZONE';
}
if (!status.makeOrganizationId) {
return 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.ORGANIZATION';
}
if (!status.makeTeamId) {
return 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.TEAM';
}
return null;
});
constructor() {
super();
}
ngOnInit() {
this._checkSetupAndLoad();
}
private _checkSetupAndLoad() {
this.loading.set(true);
this.scenarios.set([]);
this._makeComStoreService
.loadSetupStatus()
.pipe(
tap((status) => {
this.setupStatus.set(status);
if (status.isComplete) {
this._loadScenarios();
} else {
this.scenarios.set([]);
this.loading.set(false);
}
}),
catchError((error) => {
this.setupStatus.set(null);
this.scenarios.set([]);
this.loading.set(false);
this._toastrService.error(
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_SETUP_STATUS'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return EMPTY;
}),
untilDestroyed(this)
)
.subscribe();
}
private _loadScenarios() {
this.loading.set(true);
this._makeComStoreService
.loadScenarios()
.pipe(
tap((scenarios) => this.scenarios.set(scenarios)),
catchError((error) => {
this.scenarios.set([]);
this._toastrService.error(
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_SCENARIOS'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return EMPTY;
}),
finalize(() => this.loading.set(false)),
untilDestroyed(this)
)
.subscribe();
}
toggleScenario(scenario: IMakeComScenario) {
this.actionLoading.update((prev) => ({ ...prev, [scenario.id]: true }));
const action$ = scenario.isEnabled
? this._makeComStoreService.stopScenario(scenario.id)
: this._makeComStoreService.startScenario(scenario.id);
action$
.pipe(
switchMap(() => this._makeComStoreService.loadScenarios()),
tap((scenarios) => {
this.scenarios.set(scenarios);
this._toastrService.success(
scenario.isEnabled
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.SCENARIO_DEACTIVATED')
: this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.SCENARIO_ACTIVATED'),
this.getTranslation('TOASTR.TITLE.SUCCESS')
);
}),
catchError((error) => {
this._toastrService.error(
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.TOGGLE_SCENARIO'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return EMPTY;
}),
finalize(() => this.actionLoading.update((prev) => ({ ...prev, [scenario.id]: false }))),
untilDestroyed(this)
)
.subscribe();
}
runScenario(scenario: IMakeComScenario) {
this.actionLoading.update((prev) => ({ ...prev, [scenario.id]: true }));
this._makeComStoreService
.runScenario(scenario.id)
.pipe(
tap(() => {
this._toastrService.success(
this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.SCENARIO_EXECUTED'),
this.getTranslation('TOASTR.TITLE.SUCCESS')
);
}),
catchError((error) => {
this._toastrService.error(
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.RUN_SCENARIO'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return EMPTY;
}),
finalize(() => this.actionLoading.update((prev) => ({ ...prev, [scenario.id]: false }))),
untilDestroyed(this)
)
.subscribe();
}
refresh() {
this._checkSetupAndLoad();
}
}
@@ -3,41 +3,164 @@
<h5>{{ 'INTEGRATIONS.MAKE_COM.SETTINGS.TITLE' | translate }}</h5>
</nb-card-header>
<nb-card-body>
@if (loading) {
<div class="d-flex justify-content-center">
@if (loading && !settings) {
<div class="d-flex justify-content-center py-4">
<nb-spinner></nb-spinner>
</div>
}
@if (!loading) {
<form class="col-xl-6 col-12" [formGroup]="form" (ngSubmit)="saveSettings()">
} @else {
<!-- Zone & Context Setup -->
<div class="setup-section mb-4">
<h6 class="section-title">
<nb-icon icon="globe-outline" class="mr-1"></nb-icon>
{{ 'INTEGRATIONS.MAKE_COM_PAGE.CONFIGURATION_TITLE' | translate }}
</h6>
<!-- Zone Selector -->
<div class="form-group">
<nb-checkbox formControlName="isEnabled">
{{ 'INTEGRATIONS.MAKE_COM.SETTINGS.ENABLE_INTEGRATION' | translate }}
</nb-checkbox>
<label id="zone-picker-label" class="label">{{ 'INTEGRATIONS.MAKE_COM_PAGE.ZONE_LABEL' | translate }}</label>
<div class="d-flex align-items-center gap-2" role="radiogroup" aria-labelledby="zone-picker-label">
@for (zone of zones; track zone) {
<button
type="button"
nbButton
[status]="selectedZone === zone ? 'primary' : 'basic'"
size="small"
(click)="onZoneChange(zone)"
[disabled]="zoneLoading"
role="radio"
[attr.aria-checked]="selectedZone === zone"
>
{{ zone | uppercase }}
</button>
}
@if (zoneLoading) {
<nb-icon icon="loader-outline" class="spin"></nb-icon>
}
</div>
<small class="text-hint mt-1 d-block">
{{ 'INTEGRATIONS.MAKE_COM_PAGE.ZONE_HINT' | translate }}
</small>
</div>
<div class="form-group">
<label for="webhookUrl" class="label">
{{ 'INTEGRATIONS.MAKE_COM.SETTINGS.WEBHOOK_URL' | translate }}
</label>
<input
nbInput
fullWidth
type="url"
id="webhookUrl"
formControlName="webhookUrl"
[placeholder]="'INTEGRATIONS.MAKE_COM.SETTINGS.WEBHOOK_URL_PLACEHOLDER' | translate"
<!-- Organization Selector -->
@if (selectedZone) {
<div class="form-group">
<label class="label">{{ 'INTEGRATIONS.MAKE_COM_PAGE.ORGANIZATION_LABEL' | translate }}</label>
@if (orgsLoading) {
<div class="d-flex align-items-center gap-2 py-1">
<nb-icon icon="loader-outline" class="spin"></nb-icon>
<small class="text-hint">{{ 'INTEGRATIONS.MAKE_COM_PAGE.LOADING_ORGANIZATIONS' | translate }}</small>
</div>
} @else if (makeOrganizations.length === 0) {
<small class="text-hint d-block">{{ 'INTEGRATIONS.MAKE_COM_PAGE.NO_ORGANIZATIONS' | translate }}</small>
} @else {
<div class="selector-list" role="listbox" [attr.aria-label]="'INTEGRATIONS.MAKE_COM_PAGE.ORGANIZATION_LABEL' | translate">
@for (org of makeOrganizations; track org.id) {
<div
role="option"
class="selector-item"
[class.selected]="selectedMakeOrg?.id === org.id"
[attr.aria-selected]="selectedMakeOrg?.id === org.id"
tabindex="0"
(click)="onMakeOrgSelect(org)"
(keydown.enter)="onMakeOrgSelect(org)"
(keydown.space)="$event.preventDefault(); onMakeOrgSelect(org)"
>
<nb-icon
[icon]="selectedMakeOrg?.id === org.id ? 'checkmark-circle-2-outline' : 'radio-button-off-outline'"
[status]="selectedMakeOrg?.id === org.id ? 'success' : 'basic'"
></nb-icon>
<span>{{ org.name }}</span>
@if (org.zone) {
<small class="text-hint ml-auto">{{ org.zone }}</small>
}
</div>
}
</div>
}
</div>
}
<!-- Team Selector -->
@if (selectedMakeOrg) {
<div class="form-group">
<label class="label">{{ 'INTEGRATIONS.MAKE_COM_PAGE.TEAM_LABEL' | translate }}</label>
@if (teamsLoading) {
<div class="d-flex align-items-center gap-2 py-1">
<nb-icon icon="loader-outline" class="spin"></nb-icon>
<small class="text-hint">{{ 'INTEGRATIONS.MAKE_COM_PAGE.LOADING_TEAMS' | translate }}</small>
</div>
} @else if (makeTeams.length === 0) {
<small class="text-hint d-block">{{ 'INTEGRATIONS.MAKE_COM_PAGE.NO_TEAMS' | translate }}</small>
} @else {
<div class="selector-list" role="listbox" [attr.aria-label]="'INTEGRATIONS.MAKE_COM_PAGE.TEAM_LABEL' | translate">
@for (team of makeTeams; track team.id) {
<div
role="option"
class="selector-item"
[class.selected]="selectedMakeTeam?.id === team.id"
[attr.aria-selected]="selectedMakeTeam?.id === team.id"
tabindex="0"
(click)="onMakeTeamSelect(team)"
(keydown.enter)="onMakeTeamSelect(team)"
(keydown.space)="$event.preventDefault(); onMakeTeamSelect(team)"
>
<nb-icon
[icon]="selectedMakeTeam?.id === team.id ? 'checkmark-circle-2-outline' : 'radio-button-off-outline'"
[status]="selectedMakeTeam?.id === team.id ? 'success' : 'basic'"
></nb-icon>
<span>{{ team.name }}</span>
</div>
}
</div>
}
</div>
}
@if (isSetupComplete) {
<nb-card accent="success" class="setup-complete-card mt-2">
<nb-card-body class="py-2 d-flex align-items-center gap-2">
<nb-icon icon="checkmark-circle-2-outline" status="success"></nb-icon>
<span class="fw-bold">{{ 'INTEGRATIONS.MAKE_COM_PAGE.CONNECTED_TO' | translate: { org: selectedMakeOrg.name, team: selectedMakeTeam.name, zone: (selectedZone | uppercase) } }}</span>
</nb-card-body>
</nb-card>
}
</div>
<hr class="my-4" />
<!-- Webhook Settings -->
<div class="webhook-section">
<h6 class="section-title">
<nb-icon icon="link-2-outline" class="mr-1"></nb-icon>
{{ 'INTEGRATIONS.MAKE_COM_PAGE.WEBHOOK_SETTINGS_TITLE' | translate }}
</h6>
<form class="col-xl-6 col-12 px-0" [formGroup]="form" (ngSubmit)="saveSettings()">
<div class="form-group">
<nb-checkbox formControlName="isEnabled">
{{ 'INTEGRATIONS.MAKE_COM.SETTINGS.ENABLE_INTEGRATION' | translate }}
</nb-checkbox>
</div>
<div class="form-group">
<label for="webhookUrl" class="label">
{{ 'INTEGRATIONS.MAKE_COM.SETTINGS.WEBHOOK_URL' | translate }}
</label>
<input
nbInput
fullWidth
type="url"
id="webhookUrl"
formControlName="webhookUrl"
[placeholder]="'INTEGRATIONS.MAKE_COM.SETTINGS.WEBHOOK_URL_PLACEHOLDER' | translate"
/>
@if (form.get('webhookUrl').invalid && form.get('webhookUrl').touched) {
<div class="text-danger">
<div class="text-danger mt-1">
@if (form.get('webhookUrl').errors?.required) {
<span>
{{ 'VALIDATION.FIELD_REQUIRED' | translate }}
</span>
<small>{{ 'VALIDATION.FIELD_REQUIRED' | translate }}</small>
}
@if (form.get('webhookUrl').errors?.pattern) {
<span>
{{ 'INTEGRATIONS.MAKE_COM.SETTINGS.WEBHOOK_URL_INVALID' | translate }}
</span>
<small>{{ 'INTEGRATIONS.MAKE_COM.SETTINGS.WEBHOOK_URL_INVALID' | translate }}</small>
}
</div>
}
@@ -45,12 +168,13 @@
<div class="actions">
<button nbButton status="primary" type="submit" [disabled]="form.invalid || loading">
@if (loading) {
<nb-icon icon="loader-outline" pack="eva"></nb-icon>
<nb-icon icon="loader-outline" pack="eva" class="spin"></nb-icon>
}
{{ 'INTEGRATIONS.MAKE_COM.SETTINGS.SAVE' | translate }}
</button>
</div>
</form>
}
</nb-card-body>
</nb-card>
</div>
}
</nb-card-body>
</nb-card>
@@ -6,12 +6,6 @@
margin-bottom: 0.5rem;
font-weight: 500;
}
.error-message {
color: var(--danger-color);
font-size: 0.875rem;
margin-top: 0.25rem;
}
}
.actions {
@@ -23,3 +17,57 @@
min-width: 120px;
}
}
.section-title {
display: flex;
align-items: center;
margin-bottom: 1rem;
font-weight: 600;
}
.selector-list {
border: 1px solid var(--gauzy-card-border);
border-radius: calc(var(--border-radius) * 1);
overflow: hidden;
}
.selector-item {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
cursor: pointer;
transition: background-color 0.15s ease;
border-bottom: 1px solid var(--gauzy-card-border);
&:last-child {
border-bottom: none;
}
&:hover {
background-color: var(--gauzy-card-hover);
}
&.selected {
background-color: var(--gauzy-sidebar-background-color);
}
}
.setup-complete-card {
nb-card-body {
font-size: 0.875rem;
}
}
.gap-2 {
gap: 0.5rem;
}
.spin {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@@ -1,25 +1,47 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { tap, catchError } from 'rxjs/operators';
import { EMPTY } from 'rxjs';
import { MakeComStoreService } from '@gauzy/ui-core/core';
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
import { tap, catchError, finalize, timeout } from 'rxjs/operators';
import { EMPTY, TimeoutError } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import { IMakeComIntegrationSettings } from '@gauzy/contracts';
import { ToastrService } from '@gauzy/ui-core/core';
import {
IMakeComIntegrationSettings,
IMakeComOrganization,
IMakeComTeam,
MakeComZone,
MAKE_COM_ZONES
} from '@gauzy/contracts';
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
import { MakeComStoreService, ToastrService } from '@gauzy/ui-core/core';
@UntilDestroy({ checkProperties: true })
@Component({
selector: 'ngx-make-com-settings',
templateUrl: './make-com-settings.component.html',
styleUrls: ['./make-com-settings.component.scss'],
standalone: false
})
export class MakeComSettingsComponent extends TranslationBaseComponent implements OnInit {
// Webhook settings form
public form: FormGroup;
public loading = false;
public settings: IMakeComIntegrationSettings = null;
// Zone & context
public zones = MAKE_COM_ZONES;
public selectedZone: MakeComZone | null = null;
public zoneLoading = false;
// Make.com organizations
public makeOrganizations: IMakeComOrganization[] = [];
public selectedMakeOrg: IMakeComOrganization | null = null;
public orgsLoading = false;
// Make.com teams
public makeTeams: IMakeComTeam[] = [];
public selectedMakeTeam: IMakeComTeam | null = null;
public teamsLoading = false;
constructor(
private readonly _fb: FormBuilder,
private readonly _makeComStoreService: MakeComStoreService,
@@ -32,8 +54,11 @@ export class MakeComSettingsComponent extends TranslationBaseComponent implement
ngOnInit() {
this._initializeForm();
this._loadSettings();
this._loadSetupStatus();
}
// ─── Webhook Settings ───────────────────────────────────────────────────
private _initializeForm() {
this.form = this._fb.group({
isEnabled: [false],
@@ -46,6 +71,7 @@ export class MakeComSettingsComponent extends TranslationBaseComponent implement
this._makeComStoreService
.loadIntegrationSettings()
.pipe(
timeout(10_000),
tap((settings: IMakeComIntegrationSettings) => {
this.settings = settings;
this.form.patchValue({
@@ -54,23 +80,20 @@ export class MakeComSettingsComponent extends TranslationBaseComponent implement
});
}),
catchError((error) => {
this._toastrService.error(
this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_SETTINGS'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
const message =
error instanceof TimeoutError
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.REQUEST_TIMEOUT')
: this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_SETTINGS');
this._toastrService.error(message, this.getTranslation('TOASTR.TITLE.ERROR'));
console.error('Error loading Make.com settings:', error);
return EMPTY;
}),
finalize(() => (this.loading = false)),
untilDestroyed(this)
)
.subscribe(() => {
this.loading = false;
});
.subscribe();
}
/**
* Save Make.com integration settings
*/
saveSettings() {
if (this.form.invalid) {
this._toastrService.error(
@@ -99,10 +122,198 @@ export class MakeComSettingsComponent extends TranslationBaseComponent implement
console.error('Error saving Make.com settings:', error);
return EMPTY;
}),
finalize(() => (this.loading = false)),
untilDestroyed(this)
)
.subscribe(() => {
this.loading = false;
});
.subscribe();
}
// ─── Setup Status & Zone Configuration ──────────────────────────────────
/**
* Load setup status to restore previously saved zone, org, and team.
*/
private _loadSetupStatus() {
this.zoneLoading = true;
this._makeComStoreService
.loadSetupStatus()
.pipe(
timeout(10_000),
tap((status) => {
this.selectedZone = status.zone as MakeComZone;
if (status.zone) {
this._loadMakeOrganizations(status.makeOrganizationId, status.makeTeamId);
}
}),
catchError((error) => {
const message =
error instanceof TimeoutError
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.REQUEST_TIMEOUT')
: error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_SETUP_STATUS');
this._toastrService.error(message, this.getTranslation('TOASTR.TITLE.ERROR'));
console.error('Error loading Make.com setup status:', error);
return EMPTY;
}),
finalize(() => (this.zoneLoading = false)),
untilDestroyed(this)
)
.subscribe();
}
onZoneChange(zone: MakeComZone) {
this.zoneLoading = true;
this._makeComStoreService
.setZone(zone)
.pipe(
timeout(10_000),
tap(() => {
this.selectedZone = zone;
this._toastrService.success(
this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.ZONE_SET', { zone: zone.toUpperCase() }),
this.getTranslation('TOASTR.TITLE.SUCCESS')
);
this.selectedMakeOrg = null;
this.selectedMakeTeam = null;
this.makeOrganizations = [];
this.makeTeams = [];
this._loadMakeOrganizations();
}),
catchError((error) => {
const message =
error instanceof TimeoutError ? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.REQUEST_TIMEOUT') : error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.SET_ZONE');
this._toastrService.error(message, this.getTranslation('TOASTR.TITLE.ERROR'));
return EMPTY;
}),
finalize(() => (this.zoneLoading = false)),
untilDestroyed(this)
)
.subscribe();
}
// ─── Make.com Organizations ─────────────────────────────────────────────
private _loadMakeOrganizations(savedOrgId?: number, savedTeamId?: number) {
this.orgsLoading = true;
this._makeComStoreService
.loadMakeOrganizations()
.pipe(
timeout(15_000),
tap((orgs) => {
this.makeOrganizations = orgs;
if (savedOrgId) {
const savedOrg = orgs.find((o) => o.id === savedOrgId);
if (savedOrg) {
this.selectedMakeOrg = savedOrg;
this._loadMakeTeams(savedOrgId, savedTeamId);
}
}
}),
catchError((error) => {
const message =
error instanceof TimeoutError
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.REQUEST_TIMEOUT')
: error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_ORGANIZATIONS');
this._toastrService.error(message, this.getTranslation('TOASTR.TITLE.ERROR'));
return EMPTY;
}),
finalize(() => (this.orgsLoading = false)),
untilDestroyed(this)
)
.subscribe();
}
onMakeOrgSelect(org: IMakeComOrganization) {
this.orgsLoading = true;
this._makeComStoreService
.selectMakeOrganization(org)
.pipe(
timeout(10_000),
tap(() => {
this.selectedMakeOrg = org;
this.selectedMakeTeam = null;
this.makeTeams = [];
this._toastrService.success(
this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.ORGANIZATION_SELECTED', { name: org.name }),
this.getTranslation('TOASTR.TITLE.SUCCESS')
);
this._loadMakeTeams(org.id);
}),
catchError((error) => {
const message =
error instanceof TimeoutError
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.REQUEST_TIMEOUT')
: error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.SELECT_ORGANIZATION');
this._toastrService.error(message, this.getTranslation('TOASTR.TITLE.ERROR'));
return EMPTY;
}),
finalize(() => (this.orgsLoading = false)),
untilDestroyed(this)
)
.subscribe();
}
// ─── Make.com Teams ─────────────────────────────────────────────────────
private _loadMakeTeams(makeOrgId: number, savedTeamId?: number) {
this.teamsLoading = true;
this._makeComStoreService
.loadMakeTeams(makeOrgId)
.pipe(
timeout(15_000),
tap((teams) => {
this.makeTeams = teams;
if (savedTeamId) {
const savedTeam = teams.find((t) => t.id === savedTeamId);
if (savedTeam) {
this.selectedMakeTeam = savedTeam;
}
}
}),
catchError((error) => {
const message =
error instanceof TimeoutError
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.REQUEST_TIMEOUT')
: error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_TEAMS');
this._toastrService.error(message, this.getTranslation('TOASTR.TITLE.ERROR'));
return EMPTY;
}),
finalize(() => (this.teamsLoading = false)),
untilDestroyed(this)
)
.subscribe();
}
onMakeTeamSelect(team: IMakeComTeam) {
this.teamsLoading = true;
this._makeComStoreService
.selectMakeTeam(team)
.pipe(
timeout(10_000),
tap(() => {
this.selectedMakeTeam = team;
this._toastrService.success(
this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SUCCESS.TEAM_SELECTED', { name: team.name }),
this.getTranslation('TOASTR.TITLE.SUCCESS')
);
}),
catchError((error) => {
const message =
error instanceof TimeoutError
? this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.REQUEST_TIMEOUT')
: error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.SELECT_TEAM');
this._toastrService.error(message, this.getTranslation('TOASTR.TITLE.ERROR'));
return EMPTY;
}),
finalize(() => (this.teamsLoading = false)),
untilDestroyed(this)
)
.subscribe();
}
// ─── Computed Flags ─────────────────────────────────────────────────────
get isSetupComplete(): boolean {
return !!this.selectedZone && !!this.selectedMakeOrg && !!this.selectedMakeTeam;
}
}
@@ -0,0 +1,75 @@
<div class="templates-container">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6>{{ 'INTEGRATIONS.MAKE_COM_PAGE.TEMPLATES' | translate }}</h6>
<button
type="button"
nbButton
size="small"
status="basic"
(click)="refresh()"
[disabled]="loading"
[attr.aria-label]="'BUTTONS.REFRESH' | translate"
>
<nb-icon icon="refresh-outline"></nb-icon>
</button>
</div>
@if (loading) {
<div class="d-flex justify-content-center py-5">
<nb-spinner></nb-spinner>
</div>
} @else if (setupMessageKey) {
<nb-card accent="warning">
<nb-card-body class="text-center py-4">
<nb-icon icon="alert-triangle-outline" class="empty-icon mb-2" status="warning"></nb-icon>
<p>{{ setupMessageKey | translate }}</p>
</nb-card-body>
</nb-card>
}
@if (!loading && setupStatus?.isComplete && templates.length === 0) {
<nb-card accent="info">
<nb-card-body class="text-center py-4">
<nb-icon icon="file-text-outline" class="empty-icon mb-2"></nb-icon>
<p>{{ 'INTEGRATIONS.MAKE_COM_PAGE.NO_TEMPLATES' | translate }}</p>
</nb-card-body>
</nb-card>
}
@if (!loading && setupStatus?.isComplete && templates.length > 0) {
<div class="template-list">
@for (template of templates; track template.id) {
<nb-card class="template-card mb-2">
<nb-card-body>
<div class="d-flex align-items-start gap-2">
<nb-icon icon="file-text-outline" status="primary" class="mt-1"></nb-icon>
<div class="template-info">
<div class="d-flex align-items-center gap-2">
<span class="template-name">{{ template.name }}</span>
@if (template.public) {
<span class="badge badge-info">{{ 'INTEGRATIONS.MAKE_COM_PAGE.TEMPLATE_PUBLIC' | translate }}</span>
}
</div>
@if (template.description) {
<small class="text-hint d-block mt-1">{{ template.description }}</small>
}
@if (template.publishedDate) {
<small class="text-hint d-block">
{{ 'INTEGRATIONS.MAKE_COM_PAGE.TEMPLATE_PUBLISHED' | translate }}: {{ template.publishedDate | date: 'mediumDate' }}
</small>
}
@if (template.usedPackages?.length) {
<div class="packages mt-1">
@for (pkg of template.usedPackages; track pkg) {
<span class="package-tag">{{ pkg }}</span>
}
</div>
}
</div>
</div>
</nb-card-body>
</nb-card>
}
</div>
}
</div>
@@ -0,0 +1,48 @@
.templates-container {
padding: 0.5rem 0;
}
.template-card {
margin-bottom: 0.5rem;
nb-card-body {
padding: 1rem;
}
}
.template-name {
font-weight: 600;
font-size: 0.95rem;
}
.badge {
font-size: 0.7rem;
padding: 0.25rem 0.5rem;
border-radius: calc(var(--border-radius) * 0.5);
font-weight: 500;
&-info {
background-color: var(--gauzy-card-2);
color: var(--gauzy-text-color-1);
}
}
.package-tag {
display: inline-block;
font-size: 0.7rem;
padding: 0.25rem 0.5rem;
margin-right: 0.25rem;
border-radius: calc(var(--border-radius) * 0.375);
background-color: var(--gauzy-card-2);
color: var(--gauzy-text-color-2);
}
.empty-icon {
font-size: 2rem;
color: var(--gauzy-text-color-2);
}
.gap-2 {
gap: 0.5rem;
}
@@ -0,0 +1,95 @@
import { Component, OnInit } from '@angular/core';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { tap, catchError, finalize } from 'rxjs/operators';
import { EMPTY } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import { IMakeComTemplate, IMakeComSetupStatus } from '@gauzy/contracts';
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
import { MakeComStoreService, ToastrService } from '@gauzy/ui-core/core';
@UntilDestroy({ checkProperties: true })
@Component({
selector: 'ngx-make-com-templates',
templateUrl: './make-com-templates.component.html',
styleUrls: ['./make-com-templates.component.scss'],
standalone: false
})
export class MakeComTemplatesComponent extends TranslationBaseComponent implements OnInit {
public templates: IMakeComTemplate[] = [];
public loading = false;
public setupStatus: IMakeComSetupStatus | null = null;
public get setupMessageKey(): string | null {
if (this.loading) return null;
const s = this.setupStatus;
if (!s || s.isComplete) return null;
if (!s.hasAccessToken) return 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.AUTHORIZE';
if (!s.zone) return 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.ZONE';
if (!s.makeOrganizationId) return 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.ORGANIZATION';
if (!s.makeTeamId) return 'INTEGRATIONS.MAKE_COM_PAGE.SETUP_REQUIRED.TEAM';
return null;
}
constructor(
private readonly _makeComStoreService: MakeComStoreService,
private readonly _toastrService: ToastrService,
public readonly translateService: TranslateService
) {
super(translateService);
}
ngOnInit() {
this._checkSetupAndLoad();
}
private _checkSetupAndLoad() {
this.loading = true;
this.templates = [];
this._makeComStoreService
.loadSetupStatus()
.pipe(
tap((status) => {
this.setupStatus = status;
if (status.isComplete) {
this._loadTemplates();
} else {
this.loading = false;
}
}),
catchError((error) => {
this.setupStatus = null;
this.loading = false;
this._toastrService.error(
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_SETUP_STATUS'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return EMPTY;
}),
untilDestroyed(this)
)
.subscribe();
}
private _loadTemplates() {
this.loading = true;
this._makeComStoreService
.loadTemplates()
.pipe(
tap((templates) => (this.templates = templates)),
catchError((error) => {
this._toastrService.error(
error?.error?.message || this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.ERRORS.LOAD_TEMPLATES'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return EMPTY;
}),
finalize(() => (this.loading = false)),
untilDestroyed(this)
)
.subscribe();
}
refresh() {
this._checkSetupAndLoad();
}
}
@@ -56,16 +56,10 @@ export class MakeComponent extends TranslationBaseComponent implements OnInit, O
.subscribe();
}
/**
* Get route for tab navigation
*/
getRoute(tabName: string) {
return `./${tabName}`;
}
/**
* Load tabs for Make.com integration
*/
private _loadTabs() {
this.tabs = [
{
@@ -75,16 +69,22 @@ export class MakeComponent extends TranslationBaseComponent implements OnInit, O
route: this.getRoute('scenarios')
},
{
title: this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.EXECUTIONS'),
icon: 'activity-outline',
title: this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.WEBHOOKS'),
icon: 'link-2-outline',
responsive: true,
route: this.getRoute('executions')
route: this.getRoute('hooks')
},
{
title: this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.HISTORY'),
icon: 'clock-outline',
title: this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.CONNECTIONS'),
icon: 'swap-outline',
responsive: true,
route: this.getRoute('history')
route: this.getRoute('connections')
},
{
title: this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.TEMPLATES'),
icon: 'file-text-outline',
responsive: true,
route: this.getRoute('templates')
},
{
title: this.getTranslation('INTEGRATIONS.MAKE_COM_PAGE.SETTINGS'),
@@ -95,9 +95,6 @@ export class MakeComponent extends TranslationBaseComponent implements OnInit, O
];
}
/**
* Load menu items for Make.com integration
*/
private _loadMenus() {
this.menus = [
{
@@ -113,9 +110,6 @@ export class MakeComponent extends TranslationBaseComponent implements OnInit, O
];
}
/**
* Load integration settings
*/
private _loadIntegrationSettings() {
if (!this.organization) return;
@@ -144,9 +138,6 @@ export class MakeComponent extends TranslationBaseComponent implements OnInit, O
.subscribe();
}
/**
* Apply translations when language changes
*/
private _applyTranslationOnTabsActions() {
this.translateService.onLangChange
.pipe(
@@ -159,9 +150,6 @@ export class MakeComponent extends TranslationBaseComponent implements OnInit, O
.subscribe();
}
/**
* Navigate to the "Integrations" page
*/
navigateToIntegrations(): void {
this._router.navigate(['/pages/integrations']);
}
@@ -2,6 +2,8 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
NbActionsModule,
NbAlertModule,
NbBadgeModule,
NbButtonModule,
NbCalendarKitModule,
NbCardModule,
@@ -24,9 +26,15 @@ import { AuthorizationComponent } from './components/make-com-authorize/make-com
import { MakeComponent } from './components/make/make.component';
import { MakeComCallbackComponent } from './components/make-com-callback/make-com-callback.component';
import { MakeComSettingsComponent } from './components/make-com-settings/make-com-settings.component';
import { MakeComScenariosComponent } from './components/make-com-scenarios/make-com-scenarios.component';
import { MakeComHooksComponent } from './components/make-com-hooks/make-com-hooks.component';
import { MakeComTemplatesComponent } from './components/make-com-templates/make-com-templates.component';
import { MakeComConnectionsComponent } from './components/make-com-connections/make-com-connections.component';
const NB_MODULES = [
NbActionsModule,
NbAlertModule,
NbBadgeModule,
NbButtonModule,
NbSpinnerModule,
NbCalendarKitModule,
@@ -44,6 +52,7 @@ const NB_MODULES = [
@NgModule({
imports: [
CommonModule,
...NB_MODULES,
TranslateModule.forChild(),
IntegrationMakeComRoutes,
@@ -51,14 +60,17 @@ const NB_MODULES = [
SelectorsModule,
SharedModule,
TableComponentsModule,
CommonModule
MakeComConnectionsComponent
],
declarations: [
IntegrationMakeComLayoutComponent,
MakeComponent,
AuthorizationComponent,
MakeComponent,
MakeComCallbackComponent,
MakeComSettingsComponent
MakeComSettingsComponent,
MakeComScenariosComponent,
MakeComHooksComponent,
MakeComTemplatesComponent
]
})
export class IntegrationMakeComUiModule {}
@@ -5,6 +5,10 @@ import { IntegrationMakeComLayoutComponent } from './integration-make-com.layout
import { MakeComponent } from './components/make/make.component';
import { MakeComCallbackComponent } from './components/make-com-callback/make-com-callback.component';
import { MakeComSettingsComponent } from './components/make-com-settings/make-com-settings.component';
import { MakeComScenariosComponent } from './components/make-com-scenarios/make-com-scenarios.component';
import { MakeComHooksComponent } from './components/make-com-hooks/make-com-hooks.component';
import { MakeComConnectionsComponent } from './components/make-com-connections/make-com-connections.component';
import { MakeComTemplatesComponent } from './components/make-com-templates/make-com-templates.component';
@NgModule({
imports: [
@@ -38,15 +42,29 @@ import { MakeComSettingsComponent } from './components/make-com-settings/make-co
},
{
path: 'scenarios',
loadChildren: () => import('@gauzy/ui-core/shared').then((m) => m.WorkInProgressModule)
component: MakeComScenariosComponent
},
{
path: 'executions',
loadChildren: () => import('@gauzy/ui-core/shared').then((m) => m.WorkInProgressModule)
redirectTo: 'hooks',
pathMatch: 'full'
},
{
path: 'history',
loadChildren: () => import('@gauzy/ui-core/shared').then((m) => m.WorkInProgressModule)
redirectTo: 'connections',
pathMatch: 'full'
},
{
path: 'hooks',
component: MakeComHooksComponent
},
{
path: 'connections',
component: MakeComConnectionsComponent
},
{
path: 'templates',
component: MakeComTemplatesComponent
},
{
path: 'settings',
@@ -0,0 +1,184 @@
/**
* Make.com API response and request interfaces.
*
* These model the Make.com REST API v2 resources that Gauzy interacts with.
* @see https://developers.make.com/api-documentation/api-reference
*/
// ─── Pagination ─────────────────────────────────────────────────────────────
export interface IMakeComPaginationParams {
/** Number of records to skip */
'pg[offset]'?: number;
/** Max results per page */
'pg[limit]'?: number;
/** Field to sort on */
'pg[sortBy]'?: string;
/** Sort direction */
'pg[sortDir]'?: 'asc' | 'desc';
}
export interface IMakeComPaginatedResponse<T> {
[key: string]: T[] | IMakeComPagination;
pg: IMakeComPagination;
}
export interface IMakeComPagination {
offset: number;
limit: number;
sortBy: string;
sortDir: string;
}
// ─── Organization ───────────────────────────────────────────────────────────
export interface IMakeComOrganization {
id: number;
name: string;
countryId?: number;
timezoneId?: number;
license?: string;
zone?: string;
}
// ─── Team ───────────────────────────────────────────────────────────────────
export interface IMakeComTeam {
id: number;
name: string;
organizationId: number;
}
// ─── Connection ─────────────────────────────────────────────────────────────
export interface IMakeComConnection {
id: number;
name: string;
accountName?: string;
accountLabel?: string;
accountType?: string;
packageName?: string;
expire?: string;
metadata?: Record<string, any>;
teamId?: number;
upgradeable?: boolean;
scoped?: boolean;
accountTheme?: string;
editable?: boolean;
}
// ─── Scenario ───────────────────────────────────────────────────────────────
export interface IMakeComScenario {
id: number;
name: string;
teamId: number;
hookId?: number;
deviceId?: number;
deviceScope?: string;
description?: string;
folderId?: number;
isEnabled?: boolean;
isPaused?: boolean;
usedPackages?: string[];
lastEdit?: string;
scheduling?: IMakeComScenarioScheduling;
islinked?: boolean;
isinvalid?: boolean;
islocked?: boolean;
createdByUser?: { id: number; name: string; email: string };
updatedByUser?: { id: number; name: string; email: string };
}
export interface IMakeComScenarioScheduling {
type: string;
interval?: number;
}
export interface IMakeComCreateScenarioParams {
teamId: number;
name: string;
blueprint: string; // JSON string
scheduling: IMakeComScenarioScheduling;
folderId?: number;
}
export interface IMakeComUpdateScenarioParams {
name?: string;
blueprint?: string;
scheduling?: IMakeComScenarioScheduling;
}
// ─── Hook (Webhook) ─────────────────────────────────────────────────────────
export interface IMakeComHook {
id: number;
name: string;
teamId: number;
url?: string;
type?: string;
typeName?: string;
packageName?: string;
theme?: string;
enabled?: boolean;
data?: Record<string, any>;
queueCount?: number;
queueLimit?: number;
}
export interface IMakeComCreateHookParams {
teamId: number;
name: string;
typeName: string;
/** Connection-related parameters */
[key: string]: any;
}
// ─── Template ───────────────────────────────────────────────────────────────
export interface IMakeComTemplate {
id: number;
name: string;
description?: string;
usedPackages?: string[];
public?: boolean;
publishedDate?: string;
teamId?: number;
}
// ─── User ───────────────────────────────────────────────────────────────────
export interface IMakeComUser {
id: number;
name: string;
email: string;
language?: string;
locale?: string;
timezoneId?: number;
countryId?: number;
}
// ─── API Error ──────────────────────────────────────────────────────────────
export interface IMakeComApiError {
message: string;
code?: string;
suberrors?: Array<{ message: string; path: string }>;
}
// ─── Zone Config ────────────────────────────────────────────────────────────
/**
* Available Make.com zones.
* The zone determines the API base URL (e.g., us2.make.com/api/v2).
*/
export type MakeComZone = 'eu1' | 'eu2' | 'us1' | 'us2';
export const MAKE_COM_ZONES: MakeComZone[] = ['eu1', 'eu2', 'us1', 'us2'];
/**
* Builds the Make.com API base URL for a given zone.
*/
export function getMakeApiBaseUrl(zone: MakeComZone): string {
return `https://${zone}.make.com/api/v2`;
}
@@ -14,6 +14,12 @@ export enum MakeSettingName {
CLIENT_ID = 'client_id',
CLIENT_SECRET = 'client_secret',
AUTH_CODE = 'auth_code',
/** The Make.com zone for API calls, e.g. "us2", "eu1" */
ZONE = 'make_zone',
/** The Make.com organization ID (their side, not Gauzy) */
MAKE_ORGANIZATION_ID = 'make_organization_id',
/** The Make.com team ID (their side, not Gauzy) */
MAKE_TEAM_ID = 'make_team_id',
}
// Define the return type for the settings
@@ -51,10 +57,7 @@ export interface IMakeComAuthConfig {
postInstallUrl: string;
}
export interface IMakeComCreateIntegration extends IBasePerTenantAndOrganizationEntityModel {
client_id: string;
client_secret: string;
}
export interface IMakeComCreateIntegration extends IBasePerTenantAndOrganizationEntityModel {}
export interface IMakeComOAuthCodeExchange {
code: string;
@@ -0,0 +1,407 @@
import {
Controller,
Get,
Post,
Patch,
Delete,
Body,
Param,
Query,
UseGuards,
ParseIntPipe,
HttpCode,
HttpStatus,
BadRequestException
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiQuery } from '@nestjs/swagger';
import { TenantPermissionGuard, PermissionGuard, Permissions } from '@gauzy/core';
import { PermissionsEnum } from '@gauzy/contracts';
import { MakeComApiService } from './make-com-api.service';
import { IMakeComPaginationParams, IMakeComScenarioScheduling, MAKE_COM_ZONES, MakeComZone } from './interfaces/make-com-api.model';
@ApiTags('Make.com API')
@UseGuards(TenantPermissionGuard, PermissionGuard)
@Permissions(PermissionsEnum.INTEGRATION_ADD, PermissionsEnum.INTEGRATION_EDIT)
@Controller('/integration/make-com/api')
export class MakeComApiController {
constructor(private readonly makeComApiService: MakeComApiService) {}
// ─── Helpers ────────────────────────────────────────────────────────────
private extractPagination(query: Record<string, any>): IMakeComPaginationParams {
const pagination: IMakeComPaginationParams = {};
if (query.offset != null) {
const offset = Number.parseInt(query.offset, 10);
if (!Number.isNaN(offset)) pagination['pg[offset]'] = offset;
}
if (query.limit != null) {
const limit = Number.parseInt(query.limit, 10);
if (!Number.isNaN(limit)) pagination['pg[limit]'] = limit;
}
if (query.sortBy) pagination['pg[sortBy]'] = query.sortBy;
if (query.sortDir) pagination['pg[sortDir]'] = query.sortDir;
return pagination;
}
// ─── Setup Status ───────────────────────────────────────────────────────
@ApiOperation({ summary: 'Get Make.com setup status (zone, org, team, token)' })
@Get('/setup-status')
async getSetupStatus(@Query('organizationId') organizationId?: string) {
return this.makeComApiService.getSetupStatus(organizationId);
}
// ─── Zone Configuration ─────────────────────────────────────────────────
@ApiOperation({ summary: 'Get available Make.com zones' })
@Get('/zones')
getAvailableZones() {
return { zones: MAKE_COM_ZONES };
}
@ApiOperation({ summary: 'Get the configured zone for this tenant' })
@Get('/zone')
async getZone(@Query('organizationId') organizationId?: string) {
const zone = await this.makeComApiService.getZone(organizationId);
return { zone };
}
@ApiOperation({ summary: 'Set the Make.com zone for this tenant' })
@Post('/zone')
async setZone(@Body() body: { zone: MakeComZone; organizationId?: string }) {
await this.makeComApiService.setZone(body.zone, body.organizationId);
return { success: true, zone: body.zone };
}
// ─── Context (Make.com org/team selection) ──────────────────────────────
@ApiOperation({ summary: 'Set the Make.com organization ID for this tenant' })
@Post('/context/organization')
async setMakeOrganization(@Body() body: { makeOrganizationId: number; organizationId?: string }) {
if (!body.makeOrganizationId || Number.isNaN(Number(body.makeOrganizationId))) {
throw new BadRequestException('A valid makeOrganizationId is required');
}
await this.makeComApiService.setMakeOrganizationId(body.makeOrganizationId, body.organizationId);
return { success: true, makeOrganizationId: body.makeOrganizationId };
}
@ApiOperation({ summary: 'Set the Make.com team ID for this tenant' })
@Post('/context/team')
async setMakeTeam(@Body() body: { makeTeamId: number; organizationId?: string }) {
if (!body.makeTeamId || Number.isNaN(Number(body.makeTeamId))) {
throw new BadRequestException('A valid makeTeamId is required');
}
await this.makeComApiService.setMakeTeamId(body.makeTeamId, body.organizationId);
return { success: true, makeTeamId: body.makeTeamId };
}
// ─── Organizations ──────────────────────────────────────────────────────
@ApiOperation({ summary: 'List Make.com organizations for the authenticated user' })
@Get('/organizations')
async listOrganizations(@Query('organizationId') organizationId?: string) {
const organizations = await this.makeComApiService.listOrganizations(organizationId);
return { organizations };
}
@ApiOperation({ summary: 'Get a Make.com organization by ID' })
@Get('/organizations/:id')
async getOrganization(
@Param('id', ParseIntPipe) id: number,
@Query('organizationId') organizationId?: string
) {
const organization = await this.makeComApiService.getOrganization(id, organizationId);
return { organization };
}
// ─── Teams ──────────────────────────────────────────────────────────────
@ApiOperation({ summary: 'List Make.com teams' })
@ApiQuery({ name: 'makeOrgId', required: false, type: Number })
@Get('/teams')
async listTeams(
@Query('makeOrgId') makeOrgId?: string,
@Query('organizationId') organizationId?: string,
@Query() query?: Record<string, any>
) {
const parsedOrgId = makeOrgId ? Number.parseInt(makeOrgId, 10) : undefined;
const validOrgId = parsedOrgId !== undefined && !Number.isNaN(parsedOrgId) ? parsedOrgId : undefined;
const teams = await this.makeComApiService.listTeams(
validOrgId,
organizationId,
this.extractPagination(query)
);
return { teams };
}
@ApiOperation({ summary: 'Get a Make.com team by ID' })
@Get('/teams/:id')
async getTeam(
@Param('id', ParseIntPipe) id: number,
@Query('organizationId') organizationId?: string
) {
const team = await this.makeComApiService.getTeam(id, organizationId);
return { team };
}
// ─── Connections ────────────────────────────────────────────────────────
@ApiOperation({ summary: 'List Make.com connections' })
@ApiQuery({ name: 'teamId', required: false, type: Number })
@Get('/connections')
async listConnections(
@Query('teamId') teamId?: string,
@Query('organizationId') organizationId?: string,
@Query() query?: Record<string, any>
) {
const connections = await this.makeComApiService.listConnections(
teamId ? Number.parseInt(teamId, 10) : undefined,
organizationId,
this.extractPagination(query)
);
return { connections };
}
@ApiOperation({ summary: 'Get a Make.com connection by ID' })
@Get('/connections/:id')
async getConnection(
@Param('id', ParseIntPipe) id: number,
@Query('organizationId') organizationId?: string
) {
const connection = await this.makeComApiService.getConnection(id, organizationId);
return { connection };
}
@ApiOperation({ summary: 'Delete a Make.com connection' })
@Delete('/connections/:id')
@HttpCode(HttpStatus.NO_CONTENT)
async deleteConnection(
@Param('id', ParseIntPipe) id: number,
@Query('organizationId') organizationId?: string
) {
await this.makeComApiService.deleteConnection(id, organizationId);
}
@ApiOperation({ summary: 'Test a Make.com connection' })
@Post('/connections/:id/test')
async testConnection(
@Param('id', ParseIntPipe) id: number,
@Query('organizationId') organizationId?: string
) {
return this.makeComApiService.testConnection(id, organizationId);
}
// ─── Scenarios ──────────────────────────────────────────────────────────
@ApiOperation({ summary: 'List Make.com scenarios' })
@ApiQuery({ name: 'teamId', required: false, type: Number })
@Get('/scenarios')
async listScenarios(
@Query('teamId') teamId?: string,
@Query('organizationId') organizationId?: string,
@Query() query?: Record<string, any>
) {
const scenarios = await this.makeComApiService.listScenarios(
teamId ? Number.parseInt(teamId, 10) : undefined,
organizationId,
this.extractPagination(query)
);
return { scenarios };
}
@ApiOperation({ summary: 'Get a Make.com scenario by ID' })
@Get('/scenarios/:id')
async getScenario(
@Param('id', ParseIntPipe) id: number,
@Query('organizationId') organizationId?: string
) {
const scenario = await this.makeComApiService.getScenario(id, organizationId);
return { scenario };
}
@ApiOperation({ summary: 'Create a Make.com scenario' })
@Post('/scenarios')
async createScenario(
@Body() body: { teamId: number; name: string; blueprint: string; scheduling: IMakeComScenarioScheduling; folderId?: number },
@Query('organizationId') organizationId?: string
) {
const scenario = await this.makeComApiService.createScenario(body, organizationId);
return { scenario };
}
@ApiOperation({ summary: 'Update a Make.com scenario' })
@Patch('/scenarios/:id')
async updateScenario(
@Param('id', ParseIntPipe) id: number,
@Body() body: { name?: string; blueprint?: string; scheduling?: IMakeComScenarioScheduling },
@Query('organizationId') organizationId?: string
) {
const scenario = await this.makeComApiService.updateScenario(id, body, organizationId);
return { scenario };
}
@ApiOperation({ summary: 'Delete a Make.com scenario' })
@Delete('/scenarios/:id')
@HttpCode(HttpStatus.NO_CONTENT)
async deleteScenario(
@Param('id', ParseIntPipe) id: number,
@Query('organizationId') organizationId?: string
) {
await this.makeComApiService.deleteScenario(id, organizationId);
}
@ApiOperation({ summary: 'Activate a Make.com scenario' })
@Post('/scenarios/:id/start')
async startScenario(
@Param('id', ParseIntPipe) id: number,
@Query('organizationId') organizationId?: string
) {
const scenario = await this.makeComApiService.startScenario(id, organizationId);
return { scenario };
}
@ApiOperation({ summary: 'Deactivate a Make.com scenario' })
@Post('/scenarios/:id/stop')
async stopScenario(
@Param('id', ParseIntPipe) id: number,
@Query('organizationId') organizationId?: string
) {
const scenario = await this.makeComApiService.stopScenario(id, organizationId);
return { scenario };
}
@ApiOperation({ summary: 'Run a Make.com scenario on demand' })
@Post('/scenarios/:id/run')
async runScenario(
@Param('id', ParseIntPipe) id: number,
@Body() body?: { responsive?: boolean; data?: any },
@Query('organizationId') organizationId?: string
) {
return this.makeComApiService.runScenario(id, organizationId, body);
}
// ─── Hooks (Webhooks) ───────────────────────────────────────────────────
@ApiOperation({ summary: 'List Make.com hooks (webhooks)' })
@ApiQuery({ name: 'teamId', required: false, type: Number })
@Get('/hooks')
async listHooks(
@Query('teamId') teamId?: string,
@Query('organizationId') organizationId?: string,
@Query() query?: Record<string, any>
) {
const hooks = await this.makeComApiService.listHooks(
teamId ? Number.parseInt(teamId, 10) : undefined,
organizationId,
this.extractPagination(query)
);
return { hooks };
}
@ApiOperation({ summary: 'Get a Make.com hook by ID' })
@Get('/hooks/:id')
async getHook(
@Param('id', ParseIntPipe) id: number,
@Query('organizationId') organizationId?: string
) {
const hook = await this.makeComApiService.getHook(id, organizationId);
return { hook };
}
@ApiOperation({ summary: 'Create a Make.com hook (webhook)' })
@Post('/hooks')
async createHook(
@Body() body: { teamId: number; name: string; typeName: string; [key: string]: unknown },
@Query('organizationId') organizationId?: string
) {
const hook = await this.makeComApiService.createHook(body, organizationId);
return { hook };
}
@ApiOperation({ summary: 'Update a Make.com hook name' })
@Patch('/hooks/:id')
async updateHook(
@Param('id', ParseIntPipe) id: number,
@Body() body: { name: string },
@Query('organizationId') organizationId?: string
) {
const hook = await this.makeComApiService.updateHook(id, body.name, organizationId);
return { hook };
}
@ApiOperation({ summary: 'Delete a Make.com hook' })
@Delete('/hooks/:id')
@HttpCode(HttpStatus.NO_CONTENT)
async deleteHook(
@Param('id', ParseIntPipe) id: number,
@Query('organizationId') organizationId?: string
) {
await this.makeComApiService.deleteHook(id, organizationId);
}
@ApiOperation({ summary: 'Ping a Make.com hook' })
@Get('/hooks/:id/ping')
async pingHook(
@Param('id', ParseIntPipe) id: number,
@Query('organizationId') organizationId?: string
) {
return this.makeComApiService.pingHook(id, organizationId);
}
@ApiOperation({ summary: 'Enable a Make.com hook' })
@Post('/hooks/:id/enable')
async enableHook(
@Param('id', ParseIntPipe) id: number,
@Query('organizationId') organizationId?: string
) {
await this.makeComApiService.enableHook(id, organizationId);
return { success: true };
}
@ApiOperation({ summary: 'Disable a Make.com hook' })
@Post('/hooks/:id/disable')
async disableHook(
@Param('id', ParseIntPipe) id: number,
@Query('organizationId') organizationId?: string
) {
await this.makeComApiService.disableHook(id, organizationId);
return { success: true };
}
// ─── Templates ──────────────────────────────────────────────────────────
@ApiOperation({ summary: 'List Make.com templates' })
@ApiQuery({ name: 'teamId', required: false, type: Number })
@Get('/templates')
async listTemplates(
@Query('teamId') teamId?: string,
@Query('organizationId') organizationId?: string,
@Query() query?: Record<string, any>
) {
const templates = await this.makeComApiService.listTemplates(
teamId ? Number.parseInt(teamId, 10) : undefined,
organizationId,
this.extractPagination(query)
);
return { templates };
}
@ApiOperation({ summary: 'Get a Make.com template by ID' })
@Get('/templates/:id')
async getTemplate(
@Param('id', ParseIntPipe) id: number,
@Query('organizationId') organizationId?: string
) {
const template = await this.makeComApiService.getTemplate(id, organizationId);
return { template };
}
@ApiOperation({ summary: 'Get the blueprint of a Make.com template' })
@Get('/templates/:id/blueprint')
async getTemplateBlueprint(
@Param('id', ParseIntPipe) id: number,
@Query('organizationId') organizationId?: string
) {
return this.makeComApiService.getTemplateBlueprint(id, organizationId);
}
}
@@ -0,0 +1,666 @@
import { Injectable, Logger, BadRequestException, NotFoundException, HttpException, HttpStatus, UnauthorizedException } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { AxiosError, AxiosRequestConfig } from 'axios';
import { firstValueFrom, catchError } from 'rxjs';
import { IntegrationEnum } from '@gauzy/contracts';
import { IntegrationSettingService, IntegrationTenantService, RequestContext, IntegrationTenant } from '@gauzy/core';
import { MakeSettingName } from './interfaces/make-com.model';
import {
getMakeApiBaseUrl,
IMakeComOrganization,
IMakeComTeam,
IMakeComConnection,
IMakeComScenario,
IMakeComHook,
IMakeComTemplate,
IMakeComCreateScenarioParams,
IMakeComUpdateScenarioParams,
IMakeComCreateHookParams,
IMakeComPaginationParams,
MakeComZone
} from './interfaces/make-com-api.model';
import { MakeComOAuthService } from './make-com-oauth.service';
/**
* Service for interacting with the Make.com REST API v2.
*
* Handles zone-based URL resolution, token authentication (with auto-refresh),
* and provides methods for each Make.com resource.
*
* @see https://developers.make.com/api-documentation/api-reference
*/
@Injectable()
export class MakeComApiService {
private readonly logger = new Logger(MakeComApiService.name);
private readonly refreshPromises = new Map<string, Promise<string>>();
constructor(
private readonly httpService: HttpService,
private readonly integrationTenantService: IntegrationTenantService,
private readonly integrationSettingService: IntegrationSettingService,
private readonly makeComOAuthService: MakeComOAuthService
) {}
// ─── Internal Helpers ────────────────────────────────────────────────────
/**
* Resolve the integration tenant for the current request context.
*/
private async getIntegrationTenant(organizationId?: string) {
const tenantId = RequestContext.currentTenantId();
if (!tenantId) {
throw new NotFoundException('Tenant ID not found in request context');
}
const where: any = { name: IntegrationEnum.MakeCom, tenantId };
if (organizationId) {
where.organizationId = organizationId;
}
const tenant = await this.integrationTenantService.findOneByOptions({
where,
relations: ['settings']
});
if (!tenant) {
throw new NotFoundException('Make.com integration not found for this tenant');
}
return tenant;
}
/**
* Read a single setting value from the integration tenant.
*/
private getSettingValue(integrationTenant: IntegrationTenant, name: string): string | null {
const setting = integrationTenant.settings?.find((s) => s.settingsName === name);
return setting?.settingsValue ?? null;
}
/**
* Get the access token, refreshing if expired.
*/
private async getValidAccessToken(integrationTenant: IntegrationTenant): Promise<string> {
const accessToken = this.getSettingValue(integrationTenant, MakeSettingName.ACCESS_TOKEN);
const expiresAt = this.getSettingValue(integrationTenant, MakeSettingName.EXPIRES_AT);
if (!accessToken) {
throw new BadRequestException('Make.com access token not found. Please authorize first.');
}
// Check if the token is expired or about to expire (30s buffer)
if (expiresAt && new Date(expiresAt).getTime() - 30_000 < Date.now()) {
this.logger.log('Access token expired or near expiry, refreshing...');
// Serialize refresh per tenant to avoid race conditions with rotating tokens
const tenantKey = integrationTenant.id as string;
const existing = this.refreshPromises.get(tenantKey);
if (existing) {
return existing;
}
const refreshPromise = (async () => {
await this.makeComOAuthService.refreshToken(integrationTenant.id);
const updated = await this.integrationTenantService.findOneByOptions({
where: { id: integrationTenant.id },
relations: ['settings']
});
const newToken = this.getSettingValue(updated, MakeSettingName.ACCESS_TOKEN);
if (!newToken) {
throw new BadRequestException('Failed to refresh Make.com access token');
}
return newToken;
})();
this.refreshPromises.set(tenantKey, refreshPromise);
try {
return await refreshPromise;
} finally {
this.refreshPromises.delete(tenantKey);
}
}
return accessToken;
}
/**
* Resolve the zone API base URL.
*/
private getZoneBaseUrl(integrationTenant: IntegrationTenant): string {
const zone = this.getSettingValue(integrationTenant, MakeSettingName.ZONE) as MakeComZone;
if (!zone) {
throw new BadRequestException(
'Make.com zone is not configured. Please set your zone (e.g., "us2", "eu1") first.'
);
}
return getMakeApiBaseUrl(zone);
}
/**
* Execute an authenticated request to the Make.com API.
*/
private async request<T>(
method: 'GET' | 'POST' | 'PATCH' | 'DELETE' | 'PUT',
path: string,
integrationTenant: IntegrationTenant,
options?: { data?: any; params?: Record<string, any>; timeout?: number }
): Promise<T> {
const baseUrl = this.getZoneBaseUrl(integrationTenant);
const accessToken = await this.getValidAccessToken(integrationTenant);
const config: AxiosRequestConfig = {
method,
url: `${baseUrl}${path}`,
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json'
},
params: options?.params,
data: options?.data,
timeout: options?.timeout ?? 15_000
};
try {
const response = await firstValueFrom(
this.httpService.request<T>(config).pipe(
catchError((error: AxiosError) => {
const status = error.response?.status || HttpStatus.INTERNAL_SERVER_ERROR;
const data = error.response?.data as any;
const msg = data?.message || error.message;
this.logger.error(`Make.com API ${method} ${path} failed [${status}]: ${msg}`);
if (status === 401) {
throw new UnauthorizedException(`Make.com authentication failed: ${msg}`);
}
if (status === 404) {
throw new NotFoundException(`Make.com resource not found: ${msg}`);
}
throw new HttpException(`Make.com API error: ${msg}`, status);
})
)
);
return response.data;
} catch (error) {
if (error instanceof HttpException) throw error;
this.logger.error(`Unexpected error calling Make.com API: ${error.message}`);
throw new HttpException(`Make.com API error: ${error.message}`, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* Build pagination query params.
*/
private buildPaginationParams(pagination?: IMakeComPaginationParams): Record<string, any> {
if (!pagination) return {};
const params: Record<string, any> = {};
if (pagination['pg[offset]'] != null) params['pg[offset]'] = pagination['pg[offset]'];
if (pagination['pg[limit]'] != null) params['pg[limit]'] = pagination['pg[limit]'];
if (pagination['pg[sortBy]']) params['pg[sortBy]'] = pagination['pg[sortBy]'];
if (pagination['pg[sortDir]']) params['pg[sortDir]'] = pagination['pg[sortDir]'];
return params;
}
// ─── Zone & Context Management ──────────────────────────────────────────
/**
* Set the Make.com zone for the current tenant.
*/
async setZone(zone: MakeComZone, organizationId?: string): Promise<void> {
const tenant = await this.getIntegrationTenant(organizationId);
const existing = tenant.settings?.find((s) => s.settingsName === MakeSettingName.ZONE);
if (existing) {
existing.settingsValue = zone;
await this.integrationSettingService.save(existing);
} else {
await this.integrationSettingService.save({
settingsName: MakeSettingName.ZONE,
settingsValue: zone,
integration: tenant
} as any);
}
// Clear zone-scoped org/team selections only when zone actually changes
if (existing?.settingsValue !== zone) {
const existingOrg = tenant.settings?.find((s) => s.settingsName === MakeSettingName.MAKE_ORGANIZATION_ID);
if (existingOrg?.id) {
await this.integrationSettingService.delete(existingOrg.id);
}
const existingTeam = tenant.settings?.find((s) => s.settingsName === MakeSettingName.MAKE_TEAM_ID);
if (existingTeam?.id) {
await this.integrationSettingService.delete(existingTeam.id);
}
}
this.logger.log(`Make.com zone set to "${zone}" for tenant ${tenant.tenantId}`);
}
/**
* Get the current zone for the tenant.
*/
async getZone(organizationId?: string): Promise<string | null> {
const tenant = await this.getIntegrationTenant(organizationId);
return this.getSettingValue(tenant, MakeSettingName.ZONE);
}
/**
* Store the Make.com organization ID for this tenant.
*/
async setMakeOrganizationId(makeOrgId: number, organizationId?: string): Promise<void> {
const tenant = await this.getIntegrationTenant(organizationId);
const existing = tenant.settings?.find((s) => s.settingsName === MakeSettingName.MAKE_ORGANIZATION_ID);
if (existing) {
existing.settingsValue = makeOrgId.toString();
await this.integrationSettingService.save(existing);
} else {
await this.integrationSettingService.save({
settingsName: MakeSettingName.MAKE_ORGANIZATION_ID,
settingsValue: makeOrgId.toString(),
integration: tenant
} as any);
}
}
/**
* Store the Make.com team ID for this tenant.
*/
async setMakeTeamId(makeTeamId: number, organizationId?: string): Promise<void> {
const tenant = await this.getIntegrationTenant(organizationId);
const existing = tenant.settings?.find((s) => s.settingsName === MakeSettingName.MAKE_TEAM_ID);
if (existing) {
existing.settingsValue = makeTeamId.toString();
await this.integrationSettingService.save(existing);
} else {
await this.integrationSettingService.save({
settingsName: MakeSettingName.MAKE_TEAM_ID,
settingsValue: makeTeamId.toString(),
integration: tenant
} as any);
}
}
/**
* Get the current setup status: zone, orgId, teamId, and whether an access token exists.
*/
async getSetupStatus(organizationId?: string): Promise<{
hasAccessToken: boolean;
zone: MakeComZone | null;
makeOrganizationId: number | null;
makeTeamId: number | null;
isComplete: boolean;
}> {
const tenant = await this.getIntegrationTenant(organizationId);
const accessToken = this.getSettingValue(tenant, MakeSettingName.ACCESS_TOKEN);
const zone = this.getSettingValue(tenant, MakeSettingName.ZONE);
const makeOrgId = this.getSettingValue(tenant, MakeSettingName.MAKE_ORGANIZATION_ID);
const makeTeamId = this.getSettingValue(tenant, MakeSettingName.MAKE_TEAM_ID);
const parsedOrgId = makeOrgId ? Number.parseInt(makeOrgId, 10) : null;
const parsedTeamId = makeTeamId ? Number.parseInt(makeTeamId, 10) : null;
return {
hasAccessToken: !!accessToken,
zone: zone as MakeComZone | null,
makeOrganizationId: parsedOrgId !== null && !Number.isNaN(parsedOrgId) ? parsedOrgId : null,
makeTeamId: parsedTeamId !== null && !Number.isNaN(parsedTeamId) ? parsedTeamId : null,
isComplete: !!accessToken && !!zone && !!makeOrgId && !!makeTeamId
};
}
/**
* Read the stored Make.com organization ID.
*/
private async getMakeOrganizationId(integrationTenant: IntegrationTenant): Promise<number> {
const value = this.getSettingValue(integrationTenant, MakeSettingName.MAKE_ORGANIZATION_ID);
if (!value) {
throw new BadRequestException('Make.com organization ID is not configured. Please select an organization first.');
}
const parsed = Number.parseInt(value, 10);
if (Number.isNaN(parsed)) {
throw new BadRequestException('Invalid Make.com organization ID stored in settings.');
}
return parsed;
}
/**
* Read the stored Make.com team ID.
*/
private async getMakeTeamId(integrationTenant: IntegrationTenant): Promise<number> {
const value = this.getSettingValue(integrationTenant, MakeSettingName.MAKE_TEAM_ID);
if (!value) {
throw new BadRequestException('Make.com team ID is not configured. Please select a team first.');
}
const parsed = Number.parseInt(value, 10);
if (Number.isNaN(parsed)) {
throw new BadRequestException('Invalid Make.com team ID stored in settings.');
}
return parsed;
}
// ─── Organizations ──────────────────────────────────────────────────────
/**
* List organizations the authenticated user has access to.
*/
async listOrganizations(organizationId?: string): Promise<IMakeComOrganization[]> {
const tenant = await this.getIntegrationTenant(organizationId);
const result = await this.request<{ organizations: IMakeComOrganization[] }>(
'GET', '/organizations', tenant
);
return result.organizations;
}
/**
* Get a single organization by its Make.com ID.
*/
async getOrganization(makeOrgId: number, organizationId?: string): Promise<IMakeComOrganization> {
const tenant = await this.getIntegrationTenant(organizationId);
const result = await this.request<{ organization: IMakeComOrganization }>(
'GET', `/organizations/${makeOrgId}`, tenant
);
return result.organization;
}
// ─── Teams ──────────────────────────────────────────────────────────────
/**
* List teams within a Make.com organization.
*/
async listTeams(
makeOrgId?: number,
organizationId?: string,
pagination?: IMakeComPaginationParams
): Promise<IMakeComTeam[]> {
const tenant = await this.getIntegrationTenant(organizationId);
const orgId = makeOrgId ?? await this.getMakeOrganizationId(tenant);
const params = { organizationId: orgId, ...this.buildPaginationParams(pagination) };
const result = await this.request<{ teams: IMakeComTeam[] }>(
'GET', '/teams', tenant, { params }
);
return result.teams;
}
/**
* Get a single team by ID.
*/
async getTeam(teamId: number, organizationId?: string): Promise<IMakeComTeam> {
const tenant = await this.getIntegrationTenant(organizationId);
const result = await this.request<{ team: IMakeComTeam }>(
'GET', `/teams/${teamId}`, tenant
);
return result.team;
}
// ─── Connections ────────────────────────────────────────────────────────
/**
* List connections for a team.
*/
async listConnections(
teamId?: number,
organizationId?: string,
pagination?: IMakeComPaginationParams
): Promise<IMakeComConnection[]> {
const tenant = await this.getIntegrationTenant(organizationId);
const resolvedTeamId = teamId ?? await this.getMakeTeamId(tenant);
const params = { teamId: resolvedTeamId, ...this.buildPaginationParams(pagination) };
const result = await this.request<{ connections: IMakeComConnection[] }>(
'GET', '/connections', tenant, { params }
);
return result.connections;
}
/**
* Get a single connection by ID.
*/
async getConnection(connectionId: number, organizationId?: string): Promise<IMakeComConnection> {
const tenant = await this.getIntegrationTenant(organizationId);
const result = await this.request<{ connection: IMakeComConnection }>(
'GET', `/connections/${connectionId}`, tenant
);
return result.connection;
}
/**
* Delete a connection.
*/
async deleteConnection(connectionId: number, organizationId?: string): Promise<void> {
const tenant = await this.getIntegrationTenant(organizationId);
await this.request<void>('DELETE', `/connections/${connectionId}`, tenant);
}
/**
* Test (verify) a connection.
*/
async testConnection(connectionId: number, organizationId?: string): Promise<any> {
const tenant = await this.getIntegrationTenant(organizationId);
return this.request<any>('POST', `/connections/${connectionId}/test`, tenant);
}
// ─── Scenarios ──────────────────────────────────────────────────────────
/**
* List scenarios for a team.
*/
async listScenarios(
teamId?: number,
organizationId?: string,
pagination?: IMakeComPaginationParams
): Promise<IMakeComScenario[]> {
const tenant = await this.getIntegrationTenant(organizationId);
const resolvedTeamId = teamId ?? await this.getMakeTeamId(tenant);
const params = { teamId: resolvedTeamId, ...this.buildPaginationParams(pagination) };
const result = await this.request<{ scenarios: IMakeComScenario[] }>(
'GET', '/scenarios', tenant, { params }
);
return result.scenarios;
}
/**
* Get a single scenario by ID.
*/
async getScenario(scenarioId: number, organizationId?: string): Promise<IMakeComScenario> {
const tenant = await this.getIntegrationTenant(organizationId);
const result = await this.request<{ scenario: IMakeComScenario }>(
'GET', `/scenarios/${scenarioId}`, tenant
);
return result.scenario;
}
/**
* Create a new scenario.
*/
async createScenario(params: IMakeComCreateScenarioParams, organizationId?: string): Promise<IMakeComScenario> {
const tenant = await this.getIntegrationTenant(organizationId);
const result = await this.request<{ scenario: IMakeComScenario }>(
'POST', '/scenarios', tenant, { data: params }
);
return result.scenario;
}
/**
* Update a scenario.
*/
async updateScenario(
scenarioId: number,
params: IMakeComUpdateScenarioParams,
organizationId?: string
): Promise<IMakeComScenario> {
const tenant = await this.getIntegrationTenant(organizationId);
const result = await this.request<{ scenario: IMakeComScenario }>(
'PATCH', `/scenarios/${scenarioId}`, tenant, { data: params }
);
return result.scenario;
}
/**
* Delete a scenario.
*/
async deleteScenario(scenarioId: number, organizationId?: string, confirmed = true): Promise<void> {
const tenant = await this.getIntegrationTenant(organizationId);
await this.request<void>('DELETE', `/scenarios/${scenarioId}`, tenant, {
params: { confirmed }
});
}
/**
* Activate (start) a scenario.
*/
async startScenario(scenarioId: number, organizationId?: string): Promise<IMakeComScenario> {
const tenant = await this.getIntegrationTenant(organizationId);
const result = await this.request<{ scenario: IMakeComScenario }>(
'POST', `/scenarios/${scenarioId}/start`, tenant
);
return result.scenario;
}
/**
* Deactivate (stop) a scenario.
*/
async stopScenario(scenarioId: number, organizationId?: string): Promise<IMakeComScenario> {
const tenant = await this.getIntegrationTenant(organizationId);
const result = await this.request<{ scenario: IMakeComScenario }>(
'POST', `/scenarios/${scenarioId}/stop`, tenant
);
return result.scenario;
}
/**
* Run a scenario on demand.
*/
async runScenario(
scenarioId: number,
organizationId?: string,
options?: { responsive?: boolean; data?: any }
): Promise<any> {
const tenant = await this.getIntegrationTenant(organizationId);
return this.request<any>('POST', `/scenarios/${scenarioId}/run`, tenant, {
data: options?.data,
params: options?.responsive ? { responsive: true } : undefined,
timeout: options?.responsive ? 60_000 : undefined
});
}
// ─── Hooks (Webhooks) ───────────────────────────────────────────────────
/**
* List hooks for a team.
*/
async listHooks(
teamId?: number,
organizationId?: string,
pagination?: IMakeComPaginationParams
): Promise<IMakeComHook[]> {
const tenant = await this.getIntegrationTenant(organizationId);
const resolvedTeamId = teamId ?? await this.getMakeTeamId(tenant);
const params = { teamId: resolvedTeamId, ...this.buildPaginationParams(pagination) };
const result = await this.request<{ hooks: IMakeComHook[] }>(
'GET', '/hooks', tenant, { params }
);
return result.hooks;
}
/**
* Get a single hook by ID.
*/
async getHook(hookId: number, organizationId?: string): Promise<IMakeComHook> {
const tenant = await this.getIntegrationTenant(organizationId);
const result = await this.request<{ hook: IMakeComHook }>(
'GET', `/hooks/${hookId}`, tenant
);
return result.hook;
}
/**
* Create a hook (webhook).
*/
async createHook(params: IMakeComCreateHookParams, organizationId?: string): Promise<IMakeComHook> {
const tenant = await this.getIntegrationTenant(organizationId);
const result = await this.request<{ hook: IMakeComHook }>(
'POST', '/hooks', tenant, { data: params }
);
return result.hook;
}
/**
* Delete a hook.
*/
async deleteHook(hookId: number, organizationId?: string, confirmed = true): Promise<void> {
const tenant = await this.getIntegrationTenant(organizationId);
await this.request<void>('DELETE', `/hooks/${hookId}`, tenant, {
params: { confirmed }
});
}
/**
* Update a hook name.
*/
async updateHook(hookId: number, name: string, organizationId?: string): Promise<IMakeComHook> {
const tenant = await this.getIntegrationTenant(organizationId);
const result = await this.request<{ hook: IMakeComHook }>(
'PATCH', `/hooks/${hookId}`, tenant, { data: { name } }
);
return result.hook;
}
/**
* Ping a hook (test).
*/
async pingHook(hookId: number, organizationId?: string): Promise<any> {
const tenant = await this.getIntegrationTenant(organizationId);
return this.request<any>('GET', `/hooks/${hookId}/ping`, tenant);
}
/**
* Enable a hook.
*/
async enableHook(hookId: number, organizationId?: string): Promise<void> {
const tenant = await this.getIntegrationTenant(organizationId);
await this.request<void>('POST', `/hooks/${hookId}/enable`, tenant);
}
/**
* Disable a hook.
*/
async disableHook(hookId: number, organizationId?: string): Promise<void> {
const tenant = await this.getIntegrationTenant(organizationId);
await this.request<void>('POST', `/hooks/${hookId}/disable`, tenant);
}
// ─── Templates ──────────────────────────────────────────────────────────
/**
* List templates.
*/
async listTemplates(
teamId?: number,
organizationId?: string,
pagination?: IMakeComPaginationParams
): Promise<IMakeComTemplate[]> {
const tenant = await this.getIntegrationTenant(organizationId);
const params: Record<string, any> = { ...this.buildPaginationParams(pagination) };
if (teamId) params.teamId = teamId;
const result = await this.request<{ templates: IMakeComTemplate[] }>(
'GET', '/templates', tenant, { params }
);
return result.templates;
}
/**
* Get a single template by ID.
*/
async getTemplate(templateId: number, organizationId?: string): Promise<IMakeComTemplate> {
const tenant = await this.getIntegrationTenant(organizationId);
const result = await this.request<{ template: IMakeComTemplate }>(
'GET', `/templates/${templateId}`, tenant
);
return result.template;
}
/**
* Get the blueprint of a template.
*/
async getTemplateBlueprint(templateId: number, organizationId?: string): Promise<any> {
const tenant = await this.getIntegrationTenant(organizationId);
return this.request<any>('GET', `/templates/${templateId}/blueprint`, tenant);
}
}
@@ -1,15 +1,18 @@
import { Controller, Get, Query, Res, HttpException, HttpStatus, BadRequestException } from '@nestjs/common';
import { Controller, Get, Query, Res, BadRequestException, Logger } from '@nestjs/common';
import { ConfigService } from '@gauzy/config';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { Response } from 'express';
import { Public } from '@gauzy/common';
import { IntegrationEnum } from '@gauzy/contracts';
import { MakeComOAuthService } from './make-com-oauth.service';
import { MAKE_POST_INSTALL_URL } from './make-com.config';
@ApiTags('Make.com OAuth')
@Public()
@Controller('/integration/make-com/oauth')
export class MakeComAuthorizationController {
private readonly logger = new Logger(MakeComAuthorizationController.name);
constructor(private readonly config: ConfigService, private readonly makeComOAuthService: MakeComOAuthService) {}
/**
@@ -26,11 +29,47 @@ export class MakeComAuthorizationController {
})
@Get('/authorize')
async authorize(@Query() { state }: { state?: string }) {
const authorizationUrl = await this.makeComOAuthService.getAuthorizationUrl({ state });
return {
authorizationUrl: this.makeComOAuthService.getAuthorizationUrl({ state })
authorizationUrl
};
}
/**
* Resolve the post-install redirect URL from config, with fallback to
* the statically resolved constant (which doesn't rely on dotenv-expand).
*/
private getPostInstallUrl(): string {
const fromConfig = this.config.get('makeCom')?.postInstallUrl;
// Detect unresolved env variable interpolation (e.g. "${CLIENT_BASE_URL}/...")
if (fromConfig && !fromConfig.includes('${')) {
return fromConfig;
}
return MAKE_POST_INSTALL_URL;
}
/**
* Build a redirect URL by appending query params.
* Handles hash-based Angular routing (e.g. http://host/#/path) by placing
* query params within the hash fragment, after the route path.
*/
private buildRedirectUrl(baseUrl: string, params: Record<string, string>): string {
const queryString = new URLSearchParams(params).toString();
const hashIndex = baseUrl.indexOf('#');
if (hashIndex !== -1) {
// Hash-based Angular routing: place query params after the hash route
// e.g. http://host/#/path?query instead of http://host?query#/path
const beforeHash = baseUrl.substring(0, hashIndex);
const hashPart = baseUrl.substring(hashIndex);
const separator = hashPart.includes('?') ? '&' : '?';
return `${beforeHash}${hashPart}${separator}${queryString}`;
}
const separator = baseUrl.includes('?') ? '&' : '?';
return `${baseUrl}${separator}${queryString}`;
}
/**
* Handles the callback from Make.com after user authorization.
* Exchanges the authorization code for access and refresh tokens.
@@ -54,11 +93,8 @@ export class MakeComAuthorizationController {
}: { code?: string; state?: string; error?: string; error_description?: string },
@Res() response: Response
) {
// Get the post-installation redirect URL from config
const postInstallUrl = this.config.get('makeCom')?.postInstallUrl;
if (!postInstallUrl) {
throw new HttpException('postInstallUrl not found in config', HttpStatus.INTERNAL_SERVER_ERROR);
}
const postInstallUrl = this.getPostInstallUrl();
this.logger.log(`OAuth callback received. postInstallUrl: ${postInstallUrl}`);
try {
// Handle error from Make.com
@@ -72,30 +108,28 @@ export class MakeComAuthorizationController {
throw new BadRequestException('Missing required parameters: code and state');
}
// Process the OAuth callback - store the authorization code and enable the integration
// The actual token exchange is handled by Make.com when they call our token endpoint
// Process the OAuth callback - verify state and exchange code for tokens
await this.makeComOAuthService.handleAuthorizationCallback(code, state);
// Build successful URL with access token
const urlObj = new URL(postInstallUrl);
urlObj.searchParams.set('success', 'true');
urlObj.searchParams.set('integration', IntegrationEnum.MakeCom);
urlObj.searchParams.set('message', 'Integration connected successfully');
// Redirect to the application with success params
const url = this.buildRedirectUrl(postInstallUrl, {
success: 'true',
integration: IntegrationEnum.MakeCom,
message: 'Integration connected successfully'
});
// Redirect to the application
return response.redirect(urlObj.toString());
this.logger.log(`OAuth callback successful, redirecting to: ${url}`);
return response.redirect(url);
} catch (error) {
// postInstallUrl is guaranteed to exist here due to earlier check
const errorMessage = error.response?.message || error.message || 'Failed to complete OAuth flow';
const queryParamsString = new URLSearchParams({
this.logger.error(`OAuth callback failed: ${errorMessage}`);
const url = this.buildRedirectUrl(postInstallUrl, {
success: 'false',
integration: IntegrationEnum.MakeCom,
message: errorMessage
}).toString();
});
const urlObj = new URL(postInstallUrl);
urlObj.search = queryParamsString;
const url = urlObj.toString();
return response.redirect(url);
}
}
@@ -1,5 +1,7 @@
import { Injectable, BadRequestException, Logger, HttpException, HttpStatus, NotFoundException } from '@nestjs/common';
import { Injectable, BadRequestException, Logger, HttpException, HttpStatus, NotFoundException, Inject } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { ConfigService } from '@gauzy/config';
import { CommandBus } from '@nestjs/cqrs';
import { AxiosError } from 'axios';
@@ -26,8 +28,8 @@ import { MakeComService } from './make-com.service';
@Injectable()
export class MakeComOAuthService {
private readonly logger = new Logger(MakeComOAuthService.name);
private pendingStates = new Map<string, { timestamp: number; codeVerifier?: string }>();
private static readonly STATE_TTL_MS = 10 * 60 * 1000; // 10 min
private static readonly STATE_CACHE_KEY_PREFIX = 'make_com_oauth_state:';
constructor(
private readonly httpService: HttpService,
@@ -36,30 +38,36 @@ export class MakeComOAuthService {
private readonly integrationSettingService: IntegrationSettingService,
private readonly integrationTenantService: IntegrationTenantService,
private readonly integrationService: IntegrationService,
private readonly commandBus: CommandBus
private readonly commandBus: CommandBus,
@Inject(CACHE_MANAGER) private readonly cacheManager: Cache
) {}
/**
* Generate a cryptographically secure random code verifier for PKCE
* Generate a cryptographically secure random code verifier for PKCE.
*/
private generateCodeVerifier(): string {
return randomBytes(32).toString('base64url');
}
/**
* Generate code challenge from code verifier using SHA256
* Generate code challenge from code verifier using SHA256.
*/
private generateCodeChallenge(codeVerifier: string): string {
return createHash('sha256').update(codeVerifier).digest('base64url');
}
/**
* Generate the authorization URL for the Make.com OAuth flow with PKCE support.
* Generate the authorization URL for the Make.com OAuth v2 flow with PKCE.
* Make.com requires code_challenge even for confidential clients.
*
* @see https://developers.make.com/api-documentation/authentication/oauth-flow/authorization-code-flow-with-refresh-token-confidential-clients
*/
getAuthorizationUrl(options?: { state?: string; clientId?: string; organizationId?: string }): string {
async getAuthorizationUrl(options?: { state?: string; clientId?: string; organizationId?: string }): Promise<string> {
try {
const redirectUri = this.config.get('makeCom').redirectUri;
if (!redirectUri) {
throw new Error('Make.com redirect URI is not configured');
}
const tenantId = RequestContext.currentTenantId();
const organizationId = options?.organizationId;
@@ -72,16 +80,17 @@ export class MakeComOAuthService {
// Always generate state internally to ensure consistent format
const state = Buffer.from(JSON.stringify({ tenantId, organizationId })).toString('base64url');
// Generate PKCE parameters
// Generate PKCE parameters (required by Make.com OAuth v2)
const codeVerifier = this.generateCodeVerifier();
const codeChallenge = this.generateCodeChallenge(codeVerifier);
this.storeStateForVerification(state, codeVerifier);
// Store state in Redis for later verification (async to ensure persistence)
await this.storeStateForVerification(state, codeVerifier);
// Prepare scopes according to Make.com documentation
const scopes = MAKE_DEFAULT_SCOPES.join(' ');
// Build authorization URL according to Make.com documentation with PKCE
// Build authorization URL per Make.com OAuth v2 documentation
const params = new URLSearchParams({
client_id: clientId,
redirect_uri: redirectUri,
@@ -92,64 +101,55 @@ export class MakeComOAuthService {
code_challenge_method: 'S256'
});
return `${MAKE_BASE_URL}/oauth/authorize?${params.toString()}`;
return `${MAKE_BASE_URL}/oauth/v2/authorize?${params.toString()}`;
} catch (error) {
this.logger.error('Error generating Make.com authorization URL:', error);
throw error;
}
}
async exchangeCodeForToken(code: string, state: string): Promise<IMakeComOAuthTokens> {
async exchangeCodeForToken(code: string, state: string, codeVerifier?: string): Promise<IMakeComOAuthTokens> {
try {
const stateVerification = this.verifyState(state);
if (!stateVerification.isValid) {
throw new BadRequestException('Invalid state parameter');
}
// Decode the state parameter
const decodedState = JSON.parse(Buffer.from(state, 'base64url').toString());
const { tenantId, organizationId } = decodedState;
// Set the tenant context for subsequent operations
const currentTenantId = RequestContext.currentTenantId();
if (currentTenantId !== tenantId) {
throw new BadRequestException('Tenant ID mismatch');
}
// Get client credentials from database
const integrationTenant = await this.getIntegrationTenant(tenantId, organizationId);
const clientId = await this.getSettingValue(integrationTenant, MakeSettingName.CLIENT_ID);
const clientSecret = await this.getSettingValue(integrationTenant, MakeSettingName.CLIENT_SECRET);
// Read client credentials from server-side config (env vars) — never from tenant data
const makeComConfig = this.config.get('makeCom');
const clientId = makeComConfig?.clientId;
const clientSecret = makeComConfig?.clientSecret;
if (!clientId || !clientSecret) {
throw new BadRequestException('Make.com OAuth credentials are not fully configured');
throw new BadRequestException('Make.com OAuth credentials are not configured on the server');
}
const redirectUri = this.config.get('makeCom').redirectUri;
if (!codeVerifier) {
throw new BadRequestException('Missing PKCE code verifier for token exchange');
}
// Prepare the request body for Make.com token endpoint with PKCE
const redirectUri = makeComConfig?.redirectUri;
if (!redirectUri) {
throw new BadRequestException('Make.com redirect URI is not configured on the server');
}
const tokenRequestParams = new URLSearchParams({
grant_type: 'authorization_code',
code,
redirect_uri: redirectUri,
client_id: clientId,
client_secret: clientSecret
client_secret: clientSecret,
code_verifier: codeVerifier,
redirect_uri: redirectUri
});
// Add code verifier for PKCE if available
if (!stateVerification.codeVerifier) {
throw new BadRequestException('Missing PKCE code verifier');
}
tokenRequestParams.append('code_verifier', stateVerification.codeVerifier);
const headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Accept': 'application/json'
};
// Make the token request to Make.com
// Make the token request to Make.com OAuth v2 token endpoint
const tokenResponse = await firstValueFrom(
this.httpService.post(`${MAKE_BASE_URL}/oauth/token`, tokenRequestParams, { headers, timeout: 10000 }).pipe(
this.httpService.post(`${MAKE_BASE_URL}/oauth/v2/token`, tokenRequestParams, { headers, timeout: 10000 }).pipe(
catchError((error: AxiosError) => {
this.logger.error('Error while exchanging code for token:', error.response?.data);
throw new HttpException(
@@ -298,121 +298,69 @@ export class MakeComOAuthService {
}
/**
* Verify the state parameter to prevent CSRF attacks and return code verifier for PKCE.
* Verify the state parameter to prevent CSRF attacks and return the PKCE code verifier.
* Uses Redis-backed cache for multi-replica support and process restart durability.
*/
verifyState(state: string): { isValid: boolean; codeVerifier?: string } {
const pendingState = this.pendingStates.get(state);
async verifyState(state: string): Promise<{ isValid: boolean; codeVerifier?: string }> {
const cacheKey = MakeComOAuthService.STATE_CACHE_KEY_PREFIX + state;
const pendingState = await this.cacheManager.get<{ timestamp: number; codeVerifier: string }>(cacheKey);
if (!pendingState) {
this.logger.warn(`State ${state} not found in pending states`);
return { isValid: false };
}
// State found - delete it immediately to ensure single-use (atomic consumption)
await this.cacheManager.del(cacheKey);
// Check expiration by reading the stored timestamp
const now = Date.now();
const expirationTime = MakeComOAuthService.STATE_TTL_MS;
if (now - pendingState.timestamp > expirationTime) {
this.logger.warn(`State ${state} has expired`);
this.pendingStates.delete(state);
return { isValid: false };
}
// Valid and used - remove it to free memory and return the code verifier
this.pendingStates.delete(state);
// Valid and not expired - return codeVerifier for token exchange
return { isValid: true, codeVerifier: pendingState.codeVerifier };
}
/**
* Store the state parameter and code verifier for later verification.
* Store the state parameter and PKCE code verifier for later verification.
* Uses Redis-backed cache with TTL for multi-replica support and process restart durability.
*
* @param {string} state - The state parameter to store.
* @param {string} codeVerifier - The PKCE code verifier to store.
*/
/**
* TODO: Implement a more robust storage mechanism for state parameters by Q4 2025.
* This is a simple in-memory storage for small scale applications.
* For scalability, we'll consider using cache, Redis for our case as it already exists in the project.
*/
private storeStateForVerification(state: string, codeVerifier?: string): void {
this.pendingStates.set(state, { timestamp: Date.now(), codeVerifier });
this.cleanupExpiredStates();
private async storeStateForVerification(state: string, codeVerifier: string): Promise<void> {
const cacheKey = MakeComOAuthService.STATE_CACHE_KEY_PREFIX + state;
await this.cacheManager.set(cacheKey, { timestamp: Date.now(), codeVerifier }, MakeComOAuthService.STATE_TTL_MS);
}
/**
* Clean up expired state parameters to prevent memory leaks.
* Clean up expired state parameters.
* Note: With Redis-backed cache and TTL, this is no longer needed as Redis handles expiration automatically.
* Kept for backward compatibility but does nothing.
*/
private cleanupExpiredStates(): void {
const now = Date.now();
const expirationTime = MakeComOAuthService.STATE_TTL_MS;
for (const [state, { timestamp }] of this.pendingStates.entries()) {
if (now - timestamp > expirationTime) {
this.pendingStates.delete(state);
}
}
// Redis TTL handles expiration automatically - no manual cleanup needed
}
/**
* Handles the callback from Make.com OAuth flow
* This method stores information for future use
* Handles the callback from Make.com OAuth flow.
* Verifies the state parameter and exchanges the authorization code for tokens using PKCE.
*/
async handleAuthorizationCallback(code: string, state: string): Promise<void> {
try {
// Decode the state to get context
const decodedState = JSON.parse(Buffer.from(state, 'base64').toString());
const { organizationId, tenantId } = decodedState;
const integrationTenant = await this.integrationTenantService.findOneByOptions({
where: {
name: IntegrationEnum.MakeCom,
tenantId,
...(organizationId && { organizationId })
},
relations: ['settings']
});
if (!integrationTenant) {
this.logger.warn(`Integration not found for tenant ${tenantId}`);
throw new NotFoundException('Integration not found');
// Verify state to prevent CSRF and retrieve the PKCE code verifier
const stateVerification = await this.verifyState(state);
if (!stateVerification.isValid) {
throw new BadRequestException('Invalid or expired state parameter');
}
// Store the authorization code
// but since Make.com handles the OAuth flow, we just store the fact
// that the integration was authorized
let authCodeSetting = integrationTenant.settings.find(
(setting) => setting.settingsName === MakeSettingName.AUTH_CODE
);
if (authCodeSetting) {
authCodeSetting.settingsValue = code;
} else {
authCodeSetting = await this.integrationSettingService.create({
settingsName: MakeSettingName.AUTH_CODE,
settingsValue: code,
integrationId: integrationTenant.id,
tenantId: integrationTenant.tenantId,
organizationId: integrationTenant.organizationId
});
}
await this.integrationSettingService.save(authCodeSetting);
// Set integration as enabled
let enabledSetting = integrationTenant.settings.find(
(setting) => setting.settingsName === MakeSettingName.IS_ENABLED
);
if (enabledSetting) {
enabledSetting.settingsValue = 'true';
} else {
enabledSetting = await this.integrationSettingService.create({
settingsName: MakeSettingName.IS_ENABLED,
settingsValue: 'true',
integrationId: integrationTenant.id,
tenantId: integrationTenant.tenantId,
organizationId: integrationTenant.organizationId
});
}
await this.integrationSettingService.save(enabledSetting);
this.logger.log(`Successfully handled OAuth callback for tenant ${tenantId}`);
// Exchange the authorization code for tokens with the PKCE code verifier
await this.exchangeCodeForToken(code, state, stateVerification.codeVerifier);
this.logger.log('Successfully handled OAuth callback and exchanged code for tokens');
} catch (error) {
this.logger.error('Error handling Make.com authorization callback:', error);
throw error;
@@ -439,11 +387,17 @@ export class MakeComOAuthService {
throw new NotFoundException('Refresh token not found for this integration');
}
// Get OAuth credentials from the database
const { clientId, clientSecret } = await this.makeComService.getOAuthCredentials(integrationId);
// Read OAuth credentials from server-side config (env vars) — never from tenant data
const makeComConfig = this.config.get('makeCom');
const clientId = makeComConfig?.clientId;
const clientSecret = makeComConfig?.clientSecret;
// Get the Make.com token endpoint URL
const tokenUrl = `${MAKE_BASE_URL}/oauth/token`;
if (!clientId || !clientSecret) {
throw new BadRequestException('Make.com OAuth credentials are not configured on the server');
}
// Get the Make.com OAuth v2 token endpoint URL
const tokenUrl = `${MAKE_BASE_URL}/oauth/v2/token`;
// Create the form data for the token request
const formData = new URLSearchParams();
@@ -9,19 +9,29 @@ const { CLIENT_BASE_URL, API_BASE_URL } = process.env;
/** Make authentication server URL */
export const MAKE_BASE_URL = process.env.GAUZY_MAKE_BASE_URL ?? 'https://www.make.com';
/**
* Resolve an env variable, falling back to a constructed or hardcoded default.
* Detects unresolved dotenv interpolation (e.g. "${VAR}") and skips it.
*/
function resolveEnvUrl(envValue: string | undefined, constructed: string | undefined, fallback: string): string {
if (envValue && !envValue.includes('${')) return envValue;
if (constructed && !constructed.includes('${')) return constructed;
return fallback;
}
/** Make post-install URL */
export const MAKE_POST_INSTALL_URL = process.env.GAUZY_MAKE_POST_INSTALL_URL ??
(CLIENT_BASE_URL ?
`${CLIENT_BASE_URL}/#/pages/integrations/make` :
'http://localhost:4200/#/pages/integrations/make'
);
export const MAKE_POST_INSTALL_URL = resolveEnvUrl(
process.env.GAUZY_MAKE_POST_INSTALL_URL,
CLIENT_BASE_URL ? `${CLIENT_BASE_URL}/#/pages/integrations/make` : undefined,
'http://localhost:4200/#/pages/integrations/make'
);
/** Make OAuth redirect URL */
export const MAKE_REDIRECT_URL = process.env.GAUZY_MAKE_REDIRECT_URL ??
(API_BASE_URL ?
`${API_BASE_URL}/api/integration/make-com/oauth/callback` :
'http://localhost:3000/api/integration/make-com/oauth/callback'
);
export const MAKE_REDIRECT_URL = resolveEnvUrl(
process.env.GAUZY_MAKE_REDIRECT_URL,
API_BASE_URL ? `${API_BASE_URL}/api/integration/make-com/oauth/callback` : undefined,
'http://localhost:3000/api/integration/make-com/oauth/callback'
);
/** Default scopes for Make.com OAuth - Updated to match Make.com documentation */
export const MAKE_DEFAULT_SCOPES =
@@ -1,10 +1,9 @@
import { Controller, Get, Post, Body, UseGuards, BadRequestException, NotFoundException, Logger } from '@nestjs/common';
import { ConfigService } from '@gauzy/config';
import { Controller, Get, Post, Body, UseGuards, BadRequestException, NotFoundException } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { TenantPermissionGuard, Permissions, PermissionGuard, RequestContext } from '@gauzy/core';
import { PermissionsEnum } from '@gauzy/contracts';
import { MakeComService } from './make-com.service';
import { IMakeComCreateIntegration, IMakeComIntegrationSettings } from './interfaces/make-com.model';
import { IMakeComIntegrationSettings } from './interfaces/make-com.model';
import { UpdateMakeComSettingsDTO } from './dto';
import { MakeComOAuthService } from './make-com-oauth.service';
@@ -15,8 +14,7 @@ import { MakeComOAuthService } from './make-com-oauth.service';
export class MakeComController {
constructor(
private readonly makeComService: MakeComService,
private readonly makeComOAuthService: MakeComOAuthService,
private readonly config: ConfigService
private readonly makeComOAuthService: MakeComOAuthService
) {}
/**
@@ -68,26 +66,25 @@ export class MakeComController {
}
/**
* Updates the Make.com OAuth settings for the current tenant.
*
* @param {UpdateMakeComSettingsDTO} input - The DTO containing the updated Make.com OAuth settings.
* @returns {Promise<IMakeComIntegrationSettings>} A promise that resolves to the updated integration settings.
* Initialize Make.com OAuth integration for the current tenant.
* Client credentials are read from server-side environment variables
* and are never exposed to tenants.
*/
@ApiOperation({ summary: 'Update Make.com OAuth settings for tenant' })
@ApiOperation({ summary: 'Initialize Make.com OAuth integration' })
@ApiResponse({
status: 200,
description: 'Make.com OAuth settings updated successfully'
description: 'Make.com OAuth authorization URL generated successfully'
})
@ApiResponse({
status: 400,
description: 'Both clientId and clientSecret must be provided together'
description: 'Server-side OAuth credentials not configured'
})
@ApiResponse({
status: 404,
description: 'Tenant ID not found in request context'
})
@Post('/oauth-settings')
async addOAuthSettings(@Body() input: IMakeComCreateIntegration): Promise<{
async initializeIntegration(@Body() body: { organizationId?: string }): Promise<{
authorizationUrl: string;
integrationId: string;
}> {
@@ -96,21 +93,14 @@ export class MakeComController {
throw new NotFoundException('Tenant ID not found in request context');
}
// Validate input
if (!input.client_id || !input.client_secret) {
throw new BadRequestException('Both client_id and client_secret are required');
}
// Save the credentials and create/update integration with encrypted secret
const integration = await this.makeComService.addIntegrationSettings({
...input,
client_secret: input.client_secret
// Generate authorization URL first — if this fails (e.g. missing config),
// we avoid leaving a partial integration record behind.
const authorizationUrl = await this.makeComOAuthService.getAuthorizationUrl({
organizationId: body?.organizationId
});
// Generate authorization URL
const authorizationUrl = this.makeComOAuthService.getAuthorizationUrl({
clientId: input.client_id
});
// Save the integration with server-side credentials
const integration = await this.makeComService.addIntegrationSettings(body?.organizationId);
return {
authorizationUrl,
@@ -118,31 +108,6 @@ export class MakeComController {
};
}
/**
* Gets the Make.com OAuth configuration for the frontend.
* Note: Client secret is intentionally excluded for security reasons.
*/
@ApiOperation({ summary: 'Get Make.com OAuth configuration' })
@ApiResponse({
status: 200,
description: 'Retrieved Make.com OAuth configuration'
})
@Get('/oauth-config')
async getOAuthConfig(): Promise<{ clientId: string; redirectUri: string }> {
// Get client ID from the database
const clientId = await this.makeComService.getOAuthClientId();
// Get redirect URI from config (this is typically an environment variable)
const makeComConfig = this.config.get('makeCom');
const redirectUri = makeComConfig?.redirectUri;
if (!clientId || !redirectUri) {
throw new BadRequestException('OAuth configuration is missing required values.');
}
return { clientId, redirectUri };
}
/**
* Handle Token requests from Make.com custom apps.
* This endpoint is called by your Make.com custom app during the OAuth flow.
@@ -185,8 +150,18 @@ export class MakeComController {
throw new BadRequestException('Missing required parameters');
}
// Exchange code for tokens
const tokenResponse = await this.makeComOAuthService.exchangeCodeForToken(body.code, body.state);
// Verify state and retrieve the PKCE code verifier
const stateVerification = await this.makeComOAuthService.verifyState(body.state);
if (!stateVerification.isValid) {
throw new BadRequestException('Invalid or expired state parameter');
}
// Exchange code for tokens with the PKCE code verifier
const tokenResponse = await this.makeComOAuthService.exchangeCodeForToken(
body.code,
body.state,
stateVerification.codeVerifier
);
// Return the token response in the format expected by Make.com
return {
@@ -11,11 +11,13 @@ import {
} from '@gauzy/core';
import { MakeComController } from './make-com.controller';
import { MakeComAuthorizationController } from './make-com-authorization.controller';
import { MakeComApiController } from './make-com-api.controller';
import { MakeComMiddleware } from './make-com.middleware';
import { MakeComService } from './make-com.service';
import { WebhookService } from './webhook.service';
import { EventHandlers } from './handlers';
import { MakeComOAuthService } from './make-com-oauth.service';
import { MakeComApiService } from './make-com-api.service';
@Module({
imports: [
@@ -28,9 +30,9 @@ import { MakeComOAuthService } from './make-com-oauth.service';
IntegrationTenantModule,
UserModule
],
controllers: [MakeComController, MakeComAuthorizationController],
providers: [WebhookService, MakeComService, MakeComOAuthService, ...EventHandlers],
exports: [WebhookService, MakeComService, MakeComOAuthService]
controllers: [MakeComController, MakeComAuthorizationController, MakeComApiController],
providers: [WebhookService, MakeComService, MakeComOAuthService, MakeComApiService, ...EventHandlers],
exports: [WebhookService, MakeComService, MakeComOAuthService, MakeComApiService]
})
export class MakeComModule implements NestModule {
/**
@@ -55,6 +57,11 @@ export class MakeComModule implements NestModule {
{
path: '/integration/make-com',
method: RequestMethod.POST
},
// Make.com API proxy routes
{
path: '/integration/make-com/api/*',
method: RequestMethod.ALL
}
);
}
@@ -1,4 +1,5 @@
import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { Injectable, Logger, NotFoundException, InternalServerErrorException } from '@nestjs/common';
import { ConfigService } from '@gauzy/config';
import {
IntegrationSettingService,
IntegrationService,
@@ -8,12 +9,10 @@ import {
PROJECT_TIED_ENTITIES,
IntegrationTenantUpdateOrCreateCommand
} from '@gauzy/core';
import { In } from 'typeorm';
import { IIntegrationEntitySetting, IIntegrationTenant, IntegrationEntity, IntegrationEnum } from '@gauzy/contracts';
import {
IMakeComIntegrationSettings,
MakeSettingName,
IMakeComCreateIntegration,
IIntegrationFilter
} from './interfaces/make-com.model';
import { CommandBus } from '@nestjs/cqrs';
@@ -24,6 +23,7 @@ export class MakeComService {
constructor(
private readonly _commandBus: CommandBus,
private readonly config: ConfigService,
private readonly integrationSettingService: IntegrationSettingService,
private readonly integrationTenantService: IntegrationTenantService,
private readonly integrationService: IntegrationService
@@ -182,25 +182,26 @@ export class MakeComService {
/**
* Add Make.com integration settings for the current tenant and organization.
* This method is used to add settings like client ID and client secret.
* It is called when the integration is first set up.
* The integration is automatically enabled (isEnabled = true) when created.
* @param {Object} settings - The settings to add.
* Client credentials (client_id, client_secret) are read from server-side
* environment variables and are never exposed to tenants.
*
* @param {string} [organizationId] - Optional organization ID for organization-level integration
* @returns The created settings.
* @returns The created integration tenant.
*/
async addIntegrationSettings(
settings: IMakeComCreateIntegration,
organizationId?: string
): Promise<IIntegrationTenant> {
async addIntegrationSettings(organizationId?: string): Promise<IIntegrationTenant> {
try {
const tenantId = RequestContext.currentTenantId();
const { client_id, client_secret } = settings;
if (!tenantId) {
throw new NotFoundException('Tenant ID not found in request context');
}
// Validate that server-side OAuth credentials are configured (do NOT persist them to tenant)
const makeComConfig = this.config?.get('makeCom');
if (!makeComConfig?.clientId || !makeComConfig?.clientSecret) {
throw new InternalServerErrorException('Make.com OAuth credentials are not configured on the server. Please set GAUZY_MAKE_CLIENT_ID and GAUZY_MAKE_CLIENT_SECRET environment variables.');
}
// Find or create the base integration
let integration = await this.integrationService.findOneByOptions({
where: { provider: IntegrationEnum.MakeCom }
@@ -248,14 +249,6 @@ export class MakeComService {
tenantId,
entitySettings: entitySettings,
settings: [
{
settingsName: MakeSettingName.CLIENT_ID,
settingsValue: client_id
},
{
settingsName: MakeSettingName.CLIENT_SECRET,
settingsValue: client_secret
},
{
// Automatically enable the integration when it's created
settingsName: MakeSettingName.IS_ENABLED,
@@ -275,145 +268,12 @@ export class MakeComService {
}
}
/**
* Saves OAuth credentials for the Make.com integration.
* This method stores the credentials in the database for persistence across server restarts.
* Retrieves the OAuth client ID for the Make.com integration from server-side config.
*
* @param {Object} credentials - The OAuth credentials to save.
* @param {string} credentials.clientId - The OAuth client ID.
* @param {string} credentials.clientSecret - The OAuth client secret.
* @returns {Promise<void>} A promise that resolves when the credentials have been saved.
* @returns {Promise<string | null>} A promise that resolves to the OAuth client ID or null if not configured.
*/
async saveOAuthCredentials(credentials: { clientId: string; clientSecret: string }): Promise<void> {
try {
const tenantId = RequestContext.currentTenantId();
if (!tenantId) {
throw new NotFoundException('Tenant ID not found in request context');
}
// Find the integration for the current tenant
const integrationTenant = await this.integrationTenantService.findOneByWhereOptions({
name: IntegrationEnum.MakeCom,
tenantId
});
if (!integrationTenant) {
throw new NotFoundException(`${IntegrationEnum.MakeCom} integration not found for this tenant`);
}
// Define the settings to be saved or updated
const settingsToSave = [
{
settingsName: MakeSettingName.CLIENT_ID,
settingsValue: credentials.clientId,
integration: integrationTenant
},
{
settingsName: MakeSettingName.CLIENT_SECRET,
settingsValue: credentials.clientSecret,
integration: integrationTenant
}
];
// Use the bulkUpdateOrCreate method to save the settings
await this.integrationSettingService.bulkUpdateOrCreate(integrationTenant.id, settingsToSave);
this.logger.log(`OAuth credentials updated for tenant: ${tenantId}`);
} catch (error) {
this.logger.error('Error saving Make.com OAuth credentials:', error);
throw error;
}
}
/**
* Retrieves the OAuth credentials for the Make.com integration.
*
* @param {string} integrationId - The ID of the integration to get credentials for.
* @returns {Promise<{clientId: string, clientSecret: string}>} A promise that resolves to the OAuth credentials.
*/
async getOAuthCredentials(
integrationId: string,
organizationId?: string
): Promise<{ clientId: string; clientSecret: string }> {
try {
// Find the integration settings
const where: any = {
integration: { id: integrationId },
settingsName: In([MakeSettingName.CLIENT_ID, MakeSettingName.CLIENT_SECRET])
};
if (organizationId) {
where.organizationId = organizationId;
}
const settings = await this.integrationSettingService.find({ where });
if (!settings || settings.length < 2) {
throw new NotFoundException(
'OAuth credentials not found for this integration missing client_id or client_secret'
);
}
// Extract client ID and client secret
const clientIdSetting = settings.find((setting) => setting.settingsName === MakeSettingName.CLIENT_ID);
const clientSecretSetting = settings.find(
(setting) => setting.settingsName === MakeSettingName.CLIENT_SECRET
);
if (!clientIdSetting || !clientSecretSetting) {
throw new NotFoundException('OAuth credentials are incomplete for this integration');
}
return {
clientId: clientIdSetting.settingsValue,
clientSecret: clientSecretSetting.settingsValue
};
} catch (error) {
this.logger.error('Error retrieving Make.com OAuth credentials:', error);
throw error;
}
}
/**
* Retrieves the OAuth client ID for the Make.com integration.
*
* @param {string} [organizationId] - Optional organization ID to filter by organization level
* @returns {Promise<string>} A promise that resolves to the OAuth client ID or null if not found.
*/
async getOAuthClientId(organizationId?: string): Promise<string | null> {
try {
const tenantId = RequestContext.currentTenantId();
if (!tenantId) {
throw new NotFoundException('Tenant ID not found in request context');
}
// Build the where clause with tenant and optional organization filter
const whereClause: any = {
name: IntegrationEnum.MakeCom,
tenantId
};
// If organizationId is provided, filter by organization level
if (organizationId) {
whereClause.organizationId = organizationId;
}
// Find the integration for the current tenant and organization
const integrationTenant = await this.integrationTenantService.findOneByOptions({
where: whereClause,
relations: ['settings']
});
if (!integrationTenant) {
return null;
}
// Find the client ID setting
const clientIdSetting = integrationTenant.settings.find(
(setting) => setting.settingsName === MakeSettingName.CLIENT_ID
);
return clientIdSetting ? clientIdSetting.settingsValue : null;
} catch (error) {
this.logger.error('Error retrieving Make.com OAuth client ID:', error);
throw error;
}
async getOAuthClientId(): Promise<string | null> {
const makeComConfig = this.config?.get('makeCom');
return makeComConfig?.clientId ?? null;
}
}
@@ -4,52 +4,23 @@
<h5>{{ 'MENU.ZAPIER' | translate }}</h5>
</nb-card-header>
<nb-card-body>
<form class="col-xl-6 col-12" [formGroup]="form" (ngSubmit)="startAuthorization()">
<div class="form-group">
<label for="client_id" class="label">{{ 'INTEGRATIONS.ZAPIER_PAGE.CLIENT_ID' | translate }}</label>
<input
fullWidth
id="client_id"
formControlName="client_id"
type="text"
nbInput
[placeholder]="'FORM.PLACEHOLDERS.ZAPIER_CLIENT_ID' | translate"
/>
@if (form.controls.client_id.touched && form.controls.client_id.errors?.required) {
<div
class="error-message"
>
{{ 'FORM.VALIDATION.REQUIRED' | translate }}
</div>
}
</div>
<div class="form-group">
<label for="client_secret" class="label">{{
'INTEGRATIONS.ZAPIER_PAGE.CLIENT_SECRET' | translate
}}</label>
<input
fullWidth
id="client_secret"
formControlName="client_secret"
type="password"
nbInput
[placeholder]="'FORM.PLACEHOLDERS.ZAPIER_CLIENT_SECRET' | translate"
/>
@if (form.controls.client_secret.touched && form.controls.client_secret.errors?.required) {
<div
class="error-message"
>
{{ 'FORM.VALIDATION.REQUIRED' | translate }}
</div>
}
</div>
<div class="hint">
<nb-icon icon="info-outline"></nb-icon>
{{ 'INTEGRATIONS.ZAPIER_PAGE.NEXT_STEP_INFO' | translate }}
</div>
<button nbButton status="primary" [disabled]="form.invalid" size="small" outline type="submit">
{{ 'BUTTONS.NEXT' | translate }}
</button>
</form>
</nb-card-body>
</nb-card>
<div class="col-xl-6 col-12">
<div class="hint mb-4">
<nb-icon icon="info-outline"></nb-icon>
{{ 'INTEGRATIONS.ZAPIER_PAGE.CONNECT_INFO' | translate }}
</div>
<button
nbButton
status="primary"
size="small"
[disabled]="loading"
(click)="connectToZapier()"
>
@if (loading) {
<nb-icon icon="loader-outline" class="spin"></nb-icon>
}
{{ 'INTEGRATIONS.ZAPIER_PAGE.CONNECT_TO_ZAPIER' | translate }}
</button>
</div>
</nb-card-body>
</nb-card>
@@ -1,12 +1,11 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { tap, catchError, finalize, switchMap } from 'rxjs/operators';
import { EMPTY } from 'rxjs';
import { ZapierService, ToastrService, IntegrationsService } from '@gauzy/ui-core/core';
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
import { TranslateService } from '@ngx-translate/core';
import { ICreateZapierIntegrationInput, IntegrationEnum, IOrganization } from '@gauzy/contracts';
import { IntegrationEnum, IOrganization } from '@gauzy/contracts';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { Store } from '@gauzy/ui-core/core';
@@ -18,13 +17,10 @@ import { Store } from '@gauzy/ui-core/core';
standalone: false
})
export class ZapierAuthorizeComponent extends TranslationBaseComponent implements OnInit {
public form: FormGroup;
public loading = false;
public oauthConfig: { clientId: string; redirectUri: string } = null;
public organization: IOrganization;
constructor(
private readonly _fb: FormBuilder,
private readonly _zapierService: ZapierService,
private readonly _toastrService: ToastrService,
private readonly _router: Router,
@@ -36,21 +32,12 @@ export class ZapierAuthorizeComponent extends TranslationBaseComponent implement
}
ngOnInit() {
this._initializeForm();
this._loadOrganization()
.pipe(
switchMap(() => this._checkExistingIntegration()),
untilDestroyed(this)
)
.subscribe();
this._loadOAuthConfig();
}
private _initializeForm() {
this.form = this._fb.group({
client_id: ['', [Validators.required]],
client_secret: ['', [Validators.required]]
});
}
private _loadOrganization() {
@@ -61,21 +48,6 @@ export class ZapierAuthorizeComponent extends TranslationBaseComponent implement
);
}
private _loadOAuthConfig() {
this._zapierService
.getOAuthConfig()
.pipe(
tap((config) => {
this.oauthConfig = config;
}),
catchError((error) => {
return EMPTY;
}),
untilDestroyed(this)
)
.subscribe();
}
private _checkExistingIntegration() {
if (!this.organization) return EMPTY;
@@ -104,31 +76,25 @@ export class ZapierAuthorizeComponent extends TranslationBaseComponent implement
}
/**
* Start OAuth authorization flow
* Start OAuth authorization flow.
* No credentials needed — server uses its own env-configured client_id/secret.
*/
startAuthorization() {
if (this.form.invalid) {
connectToZapier() {
if (!this.organization) {
this._toastrService.error(
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.ERRORS.INVALID_FORM'),
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.ERRORS.NO_ORGANIZATION'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return;
}
if (!this.organization) {
this.loading = false;
return;
}
const credentials: ICreateZapierIntegrationInput = {
...this.form.value,
organizationId: this.organization.id
};
this.loading = true;
this._zapierService
.initializeIntegration(credentials)
.initializeIntegration({ organizationId: this.organization.id })
.pipe(
tap((response: { authorizationUrl: string }) => {
// Redirect to Zapier's OAuth consent page
window.location.href = response.authorizationUrl;
}),
catchError((error) => {
@@ -0,0 +1,44 @@
<nb-card>
<nb-card-header>
<h6>{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAP_TEMPLATES.TITLE' | translate }}</h6>
</nb-card-header>
<nb-card-body>
@if (loading) {
<div class="d-flex justify-content-center">
<nb-spinner></nb-spinner>
</div>
}
@if (!loading) {
<div class="endpoints-list">
@for (template of templates; track template.id) {
<div class="endpoint-item">
<div class="endpoint-info">
<h6>{{ template.title }}</h6>
<p>{{ template.description }}</p>
</div>
<div class="endpoint-actions">
@if (template.create_url || template.url) {
<a
nbButton
status="primary"
size="small"
outline
[href]="template.create_url || template.url"
target="_blank"
rel="noopener noreferrer"
>
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAP_TEMPLATES.USE' | translate }}
</a>
}
</div>
</div>
}
@if (templates.length === 0) {
<div class="no-data">
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAP_TEMPLATES.NO_TEMPLATES' | translate }}
</div>
}
</div>
}
</nb-card-body>
</nb-card>
@@ -0,0 +1,62 @@
import { Component, OnInit } from '@angular/core';
import { tap, catchError, finalize } from 'rxjs/operators';
import { EMPTY } from 'rxjs';
import { ZapierService, ToastrService } from '@gauzy/ui-core/core';
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
import { TranslateService } from '@ngx-translate/core';
import { IZapierZapTemplate } from '@gauzy/contracts';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
@UntilDestroy({ checkProperties: true })
@Component({
selector: 'ngx-zapier-zap-templates',
templateUrl: './zapier-zap-templates.component.html',
styleUrls: ['./zapier-zap-templates.component.scss'],
standalone: false
})
export class ZapierZapTemplatesComponent extends TranslationBaseComponent implements OnInit {
public loading = false;
/** List of Zap templates available from Zapier */
public templates: IZapierZapTemplate[] = [];
constructor(
public readonly translateService: TranslateService,
private readonly _zapierService: ZapierService,
private readonly _toastrService: ToastrService
) {
super(translateService);
}
ngOnInit(): void {
this._loadTemplates();
}
/**
* Loads publicly available Zap templates. No OAuth access token is
* required — the server attaches the configured Zapier client_id.
*/
private _loadTemplates(): void {
this.loading = true;
this._zapierService
.getZapTemplates()
.pipe(
tap((templates: IZapierZapTemplate[]) => {
this.templates = templates;
}),
catchError(() => {
this._toastrService.error(
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.ERRORS.LOAD_ZAP_TEMPLATES'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return EMPTY;
}),
finalize(() => {
this.loading = false;
}),
untilDestroyed(this)
)
.subscribe();
}
}
@@ -0,0 +1,189 @@
<nb-card>
<nb-card-header class="d-flex justify-content-between align-items-center">
<h6 class="m-0">{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.TITLE' | translate }}</h6>
<button nbButton status="primary" size="small" (click)="toggleCreateForm()">
<nb-icon [icon]="showCreateForm ? 'close-outline' : 'plus-outline'"></nb-icon>
{{
(showCreateForm ? 'BUTTONS.CANCEL' : 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.CREATE') | translate
}}
</button>
</nb-card-header>
<nb-card-body>
@if (showCreateForm) {
<form [formGroup]="createZapForm" (ngSubmit)="submitCreateZap()" class="create-zap-form mb-3">
<div class="form-group">
<label class="label">
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.FIELDS.TITLE' | translate }}
</label>
<input
nbInput
fullWidth
fieldSize="small"
type="text"
formControlName="title"
[placeholder]="'INTEGRATIONS.ZAPIER_PAGE.ZAPS.TITLE_PLACEHOLDER' | translate"
/>
</div>
<div class="steps-section mt-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<label class="label m-0">
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.FIELDS.STEPS' | translate }}
</label>
<button type="button" nbButton status="basic" size="tiny" (click)="addStep()">
<nb-icon icon="plus-outline"></nb-icon>
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.ADD_STEP' | translate }}
</button>
</div>
<div formArrayName="steps">
@for (stepCtrl of stepsFormArray.controls; track $index; let i = $index) {
<div [formGroupName]="i" class="step-item p-2 mb-2">
<div class="d-flex justify-content-between align-items-center mb-2">
<strong>
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.FIELDS.STEP' | translate }} {{ i + 1 }}
</strong>
@if (stepsFormArray.length > 1) {
<button
type="button"
nbButton
status="danger"
size="tiny"
ghost
(click)="removeStep(i)"
>
<nb-icon icon="trash-2-outline"></nb-icon>
</button>
}
</div>
<div class="form-group">
<label class="label">
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.FIELDS.ACTION' | translate }}
</label>
<input
nbInput
fullWidth
fieldSize="small"
type="text"
formControlName="action"
placeholder="example_core:Vn7xbE60"
/>
</div>
<div class="form-group">
<label class="label">
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.FIELDS.AUTHENTICATION' | translate }}
</label>
<input
nbInput
fullWidth
fieldSize="small"
type="text"
formControlName="authentication"
/>
</div>
<div class="form-group">
<label class="label">
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.FIELDS.STEP_TITLE' | translate }}
</label>
<input
nbInput
fullWidth
fieldSize="small"
type="text"
formControlName="title"
/>
</div>
<div class="form-group">
<label class="label">
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.FIELDS.INPUTS' | translate }}
</label>
<textarea
nbInput
fullWidth
fieldSize="small"
rows="3"
formControlName="inputs"
placeholder='{"key": "value"}'
></textarea>
</div>
</div>
}
</div>
</div>
<div class="form-actions mt-3">
<button
type="submit"
nbButton
status="primary"
size="small"
[disabled]="creating || createZapForm.invalid"
>
@if (creating) {
<nb-icon icon="loader-outline"></nb-icon>
}
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.SUBMIT' | translate }}
</button>
</div>
</form>
}
@if (loading) {
<div class="d-flex justify-content-center">
<nb-spinner></nb-spinner>
</div>
}
@if (!loading) {
<div class="endpoints-list">
@for (zap of zaps; track zap.id) {
<div class="endpoint-item">
<div class="endpoint-info">
<h6>{{ zap.title }}</h6>
<p>
@if (zap.is_enabled !== undefined) {
<span>
{{
(zap.is_enabled
? 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.ENABLED'
: 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.DISABLED'
) | translate
}}
</span>
} @else if (zap.state) {
<span>{{ zap.state }}</span>
}
@if (zap.updated_at || zap.modified_time) {
<span> · {{ zap.updated_at || zap.modified_time }}</span>
}
</p>
</div>
<div class="endpoint-actions">
@if (zap.links?.html_editor || zap.url) {
<a
nbButton
status="primary"
size="small"
outline
[href]="zap.links?.html_editor || zap.url"
target="_blank"
rel="noopener noreferrer"
>
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.OPEN_EDITOR' | translate }}
</a>
}
</div>
</div>
}
@if (zaps.length === 0) {
<div class="no-data">
{{ 'INTEGRATIONS.ZAPIER_PAGE.ZAPS.NO_ZAPS' | translate }}
</div>
}
</div>
}
</nb-card-body>
</nb-card>
@@ -0,0 +1,280 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { tap, catchError, finalize, switchMap, map, distinctUntilChanged } from 'rxjs/operators';
import { EMPTY } from 'rxjs';
import { ZapierService, ToastrService } from '@gauzy/ui-core/core';
import { TranslationBaseComponent } from '@gauzy/ui-core/i18n';
import { TranslateService } from '@ngx-translate/core';
import { IZapierZap, IZapierZapStep, IZapierCreateZapInput, ID } from '@gauzy/contracts';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
@UntilDestroy({ checkProperties: true })
@Component({
selector: 'ngx-zapier-zaps',
templateUrl: './zapier-zaps.component.html',
styleUrls: ['./zapier-zaps.component.scss'],
standalone: false
})
export class ZapierZapsComponent extends TranslationBaseComponent implements OnInit {
public loading = false;
public creating = false;
public showCreateForm = false;
/** List of Zaps from the authenticated Zapier account */
public zaps: IZapierZap[] = [];
/** Zapier integration ID from route parameters */
public integrationId: ID | null = null;
/** Cached OAuth access token used for both read and create calls */
private _accessToken: string | null = null;
/** Reactive form used to create a new Zap */
public createZapForm: FormGroup;
constructor(
private readonly _activatedRoute: ActivatedRoute,
private readonly _fb: FormBuilder,
public readonly translateService: TranslateService,
private readonly _zapierService: ZapierService,
private readonly _toastrService: ToastrService
) {
super(translateService);
this.createZapForm = this._buildCreateZapForm();
}
ngOnInit(): void {
const parentRoute = this._activatedRoute.parent;
if (!parentRoute) {
this._showNoIntegrationError();
return;
}
parentRoute.params
.pipe(
map((p: Params) => p['id']),
distinctUntilChanged(),
tap((id: ID) => {
this.integrationId = id;
this._loadZaps();
}),
untilDestroyed(this)
)
.subscribe();
}
/**
* Accessor for the `steps` FormArray in the create-zap form.
*/
get stepsFormArray(): FormArray<FormGroup> {
return this.createZapForm.get('steps') as FormArray<FormGroup>;
}
/**
* Build the reactive form with a single starter step.
*/
private _buildCreateZapForm(): FormGroup {
return this._fb.group({
title: ['', [Validators.required, Validators.maxLength(255)]],
steps: this._fb.array([this._createStepGroup()], Validators.required)
});
}
/**
* Build a single step FormGroup with the fields supported by the
* Zapier create-a-zap API payload.
*/
private _createStepGroup(): FormGroup {
return this._fb.group({
action: ['', [Validators.required]],
authentication: [''],
title: [''],
// `inputs` is a JSON object in the API; users enter it as JSON text.
inputs: ['']
});
}
/**
* Toggle the create-zap form visibility. Resets the form when opening.
*/
toggleCreateForm(): void {
this.showCreateForm = !this.showCreateForm;
if (this.showCreateForm) {
this.createZapForm = this._buildCreateZapForm();
}
}
/**
* Append a new empty step to the create-zap form.
*/
addStep(): void {
this.stepsFormArray.push(this._createStepGroup());
}
/**
* Remove a step from the create-zap form. A Zap must have at least one step.
*/
removeStep(index: number): void {
if (this.stepsFormArray.length > 1) {
this.stepsFormArray.removeAt(index);
}
}
/**
* Submit the create-zap form and POST the payload via the Gauzy backend.
*/
submitCreateZap(): void {
if (this.createZapForm.invalid) {
this.createZapForm.markAllAsTouched();
this._toastrService.error(
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.ERRORS.INVALID_FORM'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return;
}
if (!this._accessToken) {
this._toastrService.error(
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.ERRORS.NO_ACCESS_TOKEN'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return;
}
const payload = this._toCreateZapPayload();
if (!payload) {
// Validation error toast already shown by the converter.
return;
}
this.creating = true;
this._zapierService
.createZap(payload, this._accessToken)
.pipe(
tap((zap: IZapierZap) => {
this.zaps = [zap, ...this.zaps];
this.showCreateForm = false;
this.createZapForm = this._buildCreateZapForm();
this._toastrService.success(
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.SUCCESS.CREATE_ZAP'),
this.getTranslation('TOASTR.TITLE.SUCCESS')
);
}),
catchError((error) => {
this._handleError(error, 'INTEGRATIONS.ZAPIER_PAGE.ERRORS.CREATE_ZAP');
return EMPTY;
}),
finalize(() => (this.creating = false)),
untilDestroyed(this)
)
.subscribe();
}
/**
* Convert the reactive form value into the API payload shape.
* Returns `null` if a step's `inputs` JSON cannot be parsed — in which
* case an error toast is shown.
*/
private _toCreateZapPayload(): IZapierCreateZapInput | null {
const raw = this.createZapForm.getRawValue() as {
title: string;
steps: Array<{ action: string; authentication: string; title: string; inputs: string }>;
};
const steps: IZapierZapStep[] = [];
for (const [index, step] of raw.steps.entries()) {
let inputs: Record<string, any> | null = null;
const inputsText = (step.inputs ?? '').trim();
if (inputsText.length > 0) {
try {
const parsed = JSON.parse(inputsText);
if (parsed !== null && typeof parsed !== 'object') {
throw new Error('inputs must be a JSON object');
}
inputs = parsed as Record<string, any>;
} catch {
this._toastrService.error(
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.ERRORS.INVALID_STEP_INPUTS', {
step: index + 1
}),
this.getTranslation('TOASTR.TITLE.ERROR')
);
return null;
}
}
steps.push({
action: step.action.trim(),
authentication: step.authentication?.trim() ? step.authentication.trim() : null,
title: step.title?.trim() ? step.title.trim() : null,
inputs
});
}
return {
title: raw.title.trim(),
steps
};
}
/**
* Loads the user's Zaps using the integration ID from route parameters.
*/
private _loadZaps(): void {
if (!this.integrationId) {
this._showNoIntegrationError();
return;
}
// Clear cached access token before loading a new integration to prevent
// using a stale token from a previous failed token fetch
this._accessToken = null;
this.loading = true;
this._zapierService
.getAccessToken(this.integrationId)
.pipe(
tap((accessToken: string) => (this._accessToken = accessToken)),
switchMap((accessToken: string) => this._zapierService.getZaps(accessToken)),
tap((zaps: IZapierZap[]) => {
this.zaps = zaps;
}),
catchError((error) => {
this._handleError(error, 'INTEGRATIONS.ZAPIER_PAGE.ERRORS.LOAD_ZAPS');
return EMPTY;
}),
finalize(() => {
this.loading = false;
}),
untilDestroyed(this)
)
.subscribe();
}
private _handleError(error: any, fallbackKey: string): void {
if (error?.status === 404 || error?.message?.includes('not found')) {
this._toastrService.error(
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.ERRORS.TOKEN_NOT_FOUND'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
} else if (error?.status === 401 || error?.message?.includes('access token')) {
this._toastrService.error(
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.ERRORS.INVALID_TOKEN'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
} else {
this._toastrService.error(
this.getTranslation(fallbackKey),
this.getTranslation('TOASTR.TITLE.ERROR')
);
}
}
private _showNoIntegrationError(): void {
this._toastrService.error(
this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.ERRORS.NO_INTEGRATION_ID'),
this.getTranslation('TOASTR.TITLE.ERROR')
);
}
}
@@ -87,6 +87,18 @@ export class ZapierComponent extends TranslationBaseComponent implements OnInit,
responsive: true,
route: this.getRoute('webhooks')
},
{
title: this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.TAB.ZAPS'),
icon: 'grid-outline',
responsive: true,
route: this.getRoute('zaps')
},
{
title: this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.TAB.ZAP_TEMPLATES'),
icon: 'layout-outline',
responsive: true,
route: this.getRoute('zap-templates')
},
{
title: this.getTranslation('INTEGRATIONS.ZAPIER_PAGE.TAB.SETTINGS'),
icon: 'settings-2-outline',
@@ -1,5 +1,6 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import {
NbActionsModule,
NbButtonModule,
@@ -27,6 +28,8 @@ import { ZapierSettingsComponent } from './components/zapier-settings/zapier-set
import { ZapierTriggersComponent } from './components/zapier-triggers/zapier-triggers.component';
import { ZapierActionsComponent } from './components/zapier-actions/zapier-actions.component';
import { ZapierWebhooksComponent } from './components/zapier-webhooks/zapier-webhooks.component';
import { ZapierZapsComponent } from './components/zapier-zaps/zapier-zaps.component';
import { ZapierZapTemplatesComponent } from './components/zapier-zap-templates/zapier-zap-templates.component';
const NB_MODULES = [
NbActionsModule,
@@ -54,7 +57,8 @@ const NB_MODULES = [
SelectorsModule,
SharedModule,
TableComponentsModule,
CommonModule
CommonModule,
ReactiveFormsModule
],
declarations: [
IntegrationZapierLayoutComponent,
@@ -64,7 +68,9 @@ const NB_MODULES = [
ZapierSettingsComponent,
ZapierTriggersComponent,
ZapierActionsComponent,
ZapierWebhooksComponent
ZapierWebhooksComponent,
ZapierZapsComponent,
ZapierZapTemplatesComponent
]
})
export class IntegrationZapierUiModule {}
@@ -8,6 +8,8 @@ import { ZapierSettingsComponent } from './components/zapier-settings/zapier-set
import { ZapierTriggersComponent } from './components/zapier-triggers/zapier-triggers.component';
import { ZapierActionsComponent } from './components/zapier-actions/zapier-actions.component';
import { ZapierWebhooksComponent } from './components/zapier-webhooks/zapier-webhooks.component';
import { ZapierZapsComponent } from './components/zapier-zaps/zapier-zaps.component';
import { ZapierZapTemplatesComponent } from './components/zapier-zap-templates/zapier-zap-templates.component';
@NgModule({
imports: [
@@ -51,6 +53,14 @@ import { ZapierWebhooksComponent } from './components/zapier-webhooks/zapier-web
path: 'webhooks',
component: ZapierWebhooksComponent
},
{
path: 'zaps',
component: ZapierZapsComponent
},
{
path: 'zap-templates',
component: ZapierZapTemplatesComponent
},
{
path: 'settings',
component: ZapierSettingsComponent
@@ -49,13 +49,13 @@
"rxjs": "^7.8.2",
"tslib": "^2.6.2",
"typeorm": "^0.3.28",
"zapier-platform-core": "18.2.0"
"zapier-platform-core": "^18.3.0"
},
"devDependencies": {
"@types/jest": "30.0.0",
"@types/node": "^24.10.0",
"typescript": "^5.9.3",
"zapier-platform-cli": "18.2.0"
"zapier-platform-cli": "^18.3.0"
},
"keywords": [
"zapier",
@@ -1,31 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString, IsNotEmpty, IsIn } from 'class-validator';
import { IsString, IsNotEmpty } from 'class-validator';
import { TenantOrganizationBaseDTO } from '@gauzy/core';
import { ICreateZapierIntegrationInput, ZapierGrantType } from '../zapier.types';
export class CreateZapierIntegrationDto extends TenantOrganizationBaseDTO implements ICreateZapierIntegrationInput {
export class CreateZapierIntegrationDto extends TenantOrganizationBaseDTO {
@ApiProperty({ type: () => String })
@IsString()
@IsNotEmpty()
client_id!: string;
@ApiProperty({ type: () => String })
@IsString()
@IsNotEmpty()
code!: string;
@ApiProperty({ type: () => String })
@IsString()
@IsIn(['authorization_code', 'refresh_token'])
grant_type!: ZapierGrantType;
@ApiProperty({ type: () => String })
@IsString()
@IsNotEmpty()
redirect_uri!: string;
@ApiProperty({ type: () => String })
@IsString()
@IsNotEmpty()
client_secret!: string;
override organizationId!: string;
}
@@ -1,4 +1,18 @@
import { BadRequestException, Controller, Get, HttpException, HttpStatus, Logger, Query, Res } from '@nestjs/common';
import {
BadRequestException,
Body,
Controller,
Get,
Headers,
HttpException,
HttpStatus,
Logger,
NotFoundException,
Post,
Query,
Res,
UnauthorizedException
} from '@nestjs/common';
import { ConfigService } from '@gauzy/config';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { Response } from 'express';
@@ -67,14 +81,14 @@ export class ZapierAuthorizationController {
throw new BadRequestException('Zapier post-install URL is not configured');
}
// Validate state parameter
const isValidState = await this.zapierService.parseAuthState(query.state);
if (!isValidState) {
// Validate state parameter and extract tenantId
const parsedState = await this.zapierService.parseAuthState(query.state);
if (!parsedState) {
throw new BadRequestException('Invalid or expired state parameter');
}
// Complete the OAuth flow by exchanging the code for tokens
await this.zapierService.completeOAuthFlow(query.code, query.state);
// Complete the OAuth flow — pass tenantId from state since this is a @Public() endpoint
await this.zapierService.completeOAuthFlow(query.code, query.state, parsedState.tenantId);
// Build query parameters for redirect
const queryParamsString = buildQueryString({
@@ -98,4 +112,179 @@ export class ZapierAuthorizationController {
);
}
}
/**
* OAuth token exchange endpoint — @Public() so Zapier platform can call it.
* Exchanges authorization code for access and refresh tokens.
*/
@ApiOperation({ summary: 'Exchange authorization code for tokens' })
@ApiResponse({
status: 200,
description: 'Successfully exchanged code for tokens'
})
@ApiResponse({
status: 400,
description: 'Bad request - Invalid parameters'
})
@Public()
@Post('/oauth/token')
async exchangeCodeForToken(
@Body()
body: {
code: string;
client_id: string;
client_secret: string;
redirect_uri: string;
grant_type: string;
}
) {
try {
// Validate required parameters
if (!body.code || !body.client_id || !body.client_secret || !body.redirect_uri) {
throw new BadRequestException('Missing required parameters');
}
if (body.grant_type !== 'authorization_code') {
throw new BadRequestException('Invalid grant_type. Must be "authorization_code"');
}
// Validate client credentials against server-side config (shared across all tenants)
this.zapierService.validateServerClientCredentials(body.client_id, body.client_secret);
// Resolve the correct tenant integration by the Gauzy-issued auth code
const integration = await this.zapierService.findIntegrationByAuthCode(body.code);
// Validate and consume the authorization code (single-use)
await this.zapierService.validateAndConsumeAuthCode(integration.id!, body.code, body.redirect_uri);
// Generate new tokens
const tokens = await this.zapierService.generateAndStoreNewTokens(integration.id!);
return tokens;
} catch (error) {
this.logger.error('Failed to exchange code for token', error);
if (error instanceof BadRequestException || error instanceof NotFoundException) {
throw error;
}
throw new HttpException('Failed to exchange code for token', HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* OAuth token refresh endpoint — @Public() so Zapier platform can call it.
* Refreshes an expired access token using a refresh token.
*/
@ApiOperation({ summary: 'Refresh access token' })
@ApiResponse({
status: 200,
description: 'Successfully refreshed token'
})
@ApiResponse({
status: 400,
description: 'Bad request - Invalid parameters'
})
@Public()
@Post('/oauth/refresh-token')
async refreshAccessToken(
@Body() body: { refresh_token: string; client_id: string; client_secret: string; grant_type: string }
) {
try {
// Validate required parameters
if (!body.refresh_token || !body.client_id || !body.client_secret) {
throw new BadRequestException('Missing required parameters');
}
if (body.grant_type !== 'refresh_token') {
throw new BadRequestException('Invalid grant_type. Must be "refresh_token"');
}
// Validate client credentials against server-side config (shared across all tenants)
this.zapierService.validateServerClientCredentials(body.client_id, body.client_secret);
// Resolve the correct tenant integration by the Gauzy-issued refresh token
const integration = await this.zapierService.findIntegrationByGauzyRefreshToken(body.refresh_token);
if (!integration.id) {
throw new BadRequestException('Invalid refresh token');
}
const refreshResult = await this.zapierService.refreshTokenByRefreshToken(
integration.id,
body.refresh_token
);
return refreshResult;
} catch (error) {
this.logger.error('Failed to refresh token', error);
if (error instanceof BadRequestException || error instanceof NotFoundException) {
throw error;
}
throw new HttpException('Failed to refresh token', HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* Auth test endpoint — validates a Zapier access token.
* Called by the Zapier CLI app to verify the connection is working.
*/
@ApiOperation({ summary: 'Test Zapier authentication' })
@ApiResponse({
status: 200,
description: 'Authentication is valid'
})
@ApiResponse({
status: 401,
description: 'Invalid or missing access token'
})
@Public()
@Get('/auth/test')
async testAuth(@Headers('authorization') authHeader: string) {
try {
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new UnauthorizedException('Missing or invalid Authorization header');
}
const token = authHeader.substring(7); // Strip "Bearer "
const integration = await this.zapierService.findIntegrationByToken(token);
return {
authenticated: true,
integrationId: integration.id,
name: IntegrationEnum.ZAPIER
};
} catch (error) {
this.logger.error('Zapier auth test failed', error);
throw new UnauthorizedException('Invalid access token');
}
}
/**
* Connection label endpoint — returns integration info for Zapier's UI.
* Called by the Zapier CLI app to display a label for the connected account.
*/
@ApiOperation({ summary: 'Get Zapier connection info' })
@ApiResponse({
status: 200,
description: 'Returns connection info for the Zapier account label'
})
@Public()
@Get('/auth/me')
async getConnectionInfo(@Headers('authorization') authHeader: string) {
try {
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new UnauthorizedException('Missing or invalid Authorization header');
}
const token = authHeader.substring(7);
const integration = await this.zapierService.findIntegrationByToken(token);
return {
id: integration.id,
name: IntegrationEnum.ZAPIER,
email: `zapier-integration@${integration.tenantId || 'gauzy'}`
};
} catch (error) {
this.logger.error('Zapier connection info failed', error);
throw new UnauthorizedException('Invalid access token');
}
}
}
@@ -13,14 +13,18 @@ export const ZAPIER_API_URL = 'https://api.zapier.com';
* Based on: https://docs.zapier.com/powered-by-zapier/api-reference/authentication
*
* Scopes included:
* - 'zap': Read access to Zaps (view existing Zaps)
* - 'zap': Read access to the authenticated user's own Zaps
* - 'zap:all': Read access to all Zaps the user can see (including shared)
* - 'zap:account:all': Read access to all Zaps across the user's account
* - 'zap:write': Write access to Zaps (create, update, delete Zaps)
* - 'authentication': Access to authentication information (manage user auth)
*
* These scopes provide full access to the Zapier Workflow API for managing
* Zaps and user authentication on behalf of the authenticated user.
* These scopes cover the Zapier Workflow API endpoints used by this plugin:
* GET /v2/zaps, POST /v2/zaps, GET /v2/triggers, GET /v2/actions, and the
* user authentication endpoints. The zap templates endpoint (/v1/zap-templates)
* is public and does not consume any of these scopes.
*/
export const ZAPIER_OAUTH_SCOPES = 'zap zap:write authentication';
export const ZAPIER_OAUTH_SCOPES = 'zap zap:all zap:account:all zap:write authentication';
/**
* Zapier token expiration time (in seconds)
@@ -36,7 +40,7 @@ export const ZAPIER_AUTHORIZATION_URL = `${process.env['API_BASE_URL']}/api/inte
export const ZAPIER_CALLBACK_URL = `${process.env['API_BASE_URL']}/api/integration/zapier/oauth/callback`;
/** URL for obtaining OAuth access tokens from Gauzy */
export const ZAPIER_TOKEN_URL = `${process.env['API_BASE_URL']}/api/integration/zapier/token`;
export const ZAPIER_TOKEN_URL = `${process.env['API_BASE_URL']}/api/integration/zapier/oauth/token`;
/** URL for redirecting users after OAuth authorization */
export const ZAPIER_REDIRECT_URI = 'https://zapier.com/dashboard/auth/oauth/return/App221848CLIAPI/';
@@ -13,10 +13,10 @@ import {
Param
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { PermissionsEnum } from '@gauzy/contracts';
import { IZapierCreateZapInput, IZapierZap, IZapierZapTemplate, PermissionsEnum } from '@gauzy/contracts';
import { PermissionGuard, Permissions, RequestContext, TenantPermissionGuard } from '@gauzy/core';
import { ZapierService } from './zapier.service';
import { ICreateZapierIntegrationInput, IZapierEndpoint, IZapierIntegrationSettings } from './zapier.types';
import { IZapierEndpoint, IZapierIntegrationSettings } from './zapier.types';
import { randomBytes } from 'node:crypto';
@ApiTags('Zapier Integrations')
@@ -35,9 +35,9 @@ export class ZapierController {
constructor(private readonly zapierService: ZapierService) {}
/**
* Creates a new Zapier integration.
* This method handles the initialization of a new Zapier integration by generating an authorization URL
* for the user to authorize the integration.
* Initialize a new Zapier integration.
* Stores client credentials and returns the authorization URL to redirect
* the admin to Zapier's OAuth consent page.
*/
@ApiOperation({ summary: 'Initialize a new Zapier integration' })
@ApiResponse({
@@ -49,30 +49,27 @@ export class ZapierController {
description: 'Bad Request - Missing required fields'
})
@Post('/settings')
async initializeIntegration(@Body() body: ICreateZapierIntegrationInput) {
async initializeIntegration(@Body() body: { organizationId: string }) {
const tenantId = RequestContext.currentTenantId();
if (!tenantId) {
throw new BadRequestException('Tenant ID is required');
}
// validate body inputs
if (!body.client_id || !body.client_secret) {
throw new BadRequestException('Missing required fields');
if (!body?.organizationId) {
throw new BadRequestException('Organization ID is required');
}
// Generate state parameter for CSRF protection
const state = Buffer.from(randomBytes(32)).toString('base64url');
// Store the client credentials and state for later use in the callback
// Store the integration with server-side credentials and state
const integration = await this.zapierService.storeIntegrationCredentials({
...body,
organizationId: body.organizationId,
state
});
// Generate authorization URL for user to complete OAuth flow
const authorizationUrl = this.zapierService.getAuthorizationUrl({
clientId: body.client_id,
state
});
// Generate authorization URL using server-side client ID
const authorizationUrl = this.zapierService.getAuthorizationUrl({ state });
return {
authorizationUrl,
@@ -122,6 +119,80 @@ export class ZapierController {
}
}
/**
* Get Zaps for the authenticated Zapier account.
*/
@ApiOperation({ summary: 'Get Zapier zaps' })
@ApiResponse({
status: 200,
description: 'Successfully retrieved Zapier zaps'
})
@ApiResponse({
status: 401,
description: 'Unauthorized - Invalid or missing authorization token'
})
@Get('/zaps')
async getZaps(@Query('token') token: string): Promise<IZapierZap[]> {
try {
this.validateToken(token, true);
return await this.zapierService.fetchZaps(token);
} catch (error) {
this.handleZapierError(error, 'zaps');
}
}
/**
* Create a new Zap on the authenticated Zapier account.
*/
@ApiOperation({ summary: 'Create a new Zapier zap' })
@ApiResponse({
status: 201,
description: 'Successfully created Zapier zap'
})
@ApiResponse({
status: 400,
description: 'Bad Request - Missing required fields'
})
@ApiResponse({
status: 401,
description: 'Unauthorized - Invalid or missing authorization token'
})
@Post('/zaps')
async createZap(
@Query('token') token: string,
@Body() body: IZapierCreateZapInput
): Promise<IZapierZap> {
try {
this.validateToken(token, true);
if (!body?.title || !Array.isArray(body?.steps) || body.steps.length === 0) {
throw new BadRequestException('Zap title and at least one step are required');
}
return await this.zapierService.createZap(body, token);
} catch (error) {
this.handleZapierError(error, 'zaps');
}
}
/**
* Get publicly available Zap templates from Zapier.
* This Zapier endpoint does not require an OAuth access token — it only
* needs the server-configured `client_id`, which is attached in the service.
*/
@ApiOperation({ summary: 'Get Zapier zap templates (public)' })
@ApiResponse({
status: 200,
description: 'Successfully retrieved Zapier zap templates'
})
@Get('/zap-templates')
async getZapTemplates(@Query('limit') limit?: string): Promise<IZapierZapTemplate[]> {
try {
const parsedLimit = limit ? Number(limit) : undefined;
return await this.zapierService.fetchZapTemplates(parsedLimit);
} catch (error) {
this.handleZapierError(error, 'zap-templates');
}
}
/**
* Helper method to validate Zapier token
*/
@@ -157,116 +228,6 @@ export class ZapierController {
);
}
/**
* OAuth token exchange endpoint
* Exchanges authorization code for access and refresh tokens
*/
@ApiOperation({ summary: 'Exchange authorization code for tokens' })
@ApiResponse({
status: 200,
description: 'Successfully exchanged code for tokens'
})
@ApiResponse({
status: 400,
description: 'Bad request - Invalid parameters'
})
@Post('/token')
async exchangeCodeForToken(
@Body()
body: {
code: string;
client_id: string;
client_secret: string;
redirect_uri: string;
grant_type: string;
state: string;
}
) {
try {
// Add tenant validation to token exchange endpoint
const tenantId = RequestContext.currentTenantId();
if (!tenantId) {
throw new BadRequestException('Tenant ID is required');
}
// Validate required parameters
if (!body.code || !body.client_id || !body.client_secret || !body.redirect_uri || !body.state) {
throw new BadRequestException('Missing required parameters');
}
if (body.grant_type !== 'authorization_code') {
throw new BadRequestException('Invalid grant_type. Must be "authorization_code"');
}
// Validate and delete the state parameter
const isValidState = await this.zapierService.validateAndDeleteState(body.state, tenantId);
if (!isValidState) {
throw new BadRequestException('Invalid or expired state parameter');
}
// Find the integration record using the client_id from the request
const integration = await this.zapierService.findIntegrationByClientId(body.client_id);
if (!integration.id) {
throw new BadRequestException('Invalid integration ID');
}
// Generate new tokens
const tokens = await this.zapierService.generateAndStoreNewTokens(integration.id);
return tokens;
} catch (error) {
this.logger.error('Failed to exchange code for token', error);
if (error instanceof BadRequestException || error instanceof NotFoundException) {
throw error;
}
throw new InternalServerErrorException('Failed to exchange code for token');
}
}
/**
* OAuth token refresh endpoint
* Refreshes an expired access token using a refresh token
*/
@ApiOperation({ summary: 'Refresh access token' })
@ApiResponse({
status: 200,
description: 'Successfully refreshed token'
})
@ApiResponse({
status: 400,
description: 'Bad request - Invalid parameters'
})
@Post('/refresh-token')
async refreshAccessToken(
@Body() body: { refresh_token: string; client_id: string; client_secret: string; grant_type: string }
) {
try {
// Validate required parameters
if (!body.refresh_token || !body.client_id || !body.client_secret) {
throw new BadRequestException('Missing required parameters');
}
if (body.grant_type !== 'refresh_token') {
throw new BadRequestException('Invalid grant_type. Must be "refresh_token"');
}
// Find integration by client_id and verify refresh token
const integration = await this.zapierService.findIntegrationByClientId(body.client_id);
const refreshResult = await this.zapierService.refreshTokenByRefreshToken(
integration.id ?? '',
body.refresh_token
);
return refreshResult;
} catch (error) {
this.logger.error('Failed to refresh token', error);
if (error instanceof BadRequestException || error instanceof NotFoundException) {
throw error;
}
throw new InternalServerErrorException('Failed to refresh token');
}
}
/**
* Get Zapier access token for a given integration
*/
@@ -24,12 +24,12 @@ import {
} from '@gauzy/core';
import { ZAPIER_API_URL, ZAPIER_BASE_URL, ZAPIER_TOKEN_EXPIRATION_TIME, ZAPIER_OAUTH_SCOPES } from './zapier.config';
import {
ICreateZapierIntegrationInput,
IIntegrationFilter,
IZapierAccessTokens,
IZapierEndpoint,
IZapierIntegrationSettings
} from './zapier.types';
import { IZapierCreateZapInput, IZapierZap, IZapierZapTemplate } from '@gauzy/contracts';
import { randomBytes } from 'node:crypto';
@Injectable()
@@ -75,8 +75,7 @@ export class ZapierService {
* @param {ID} integrationId - The ID of the integration.
* @returns {Promise<any>} - The new tokens.
* @throws {NotFoundException} - When no settings are found for the given integration ID
* @throws {BadRequestException} - When required settings (client_id, client_secret, refresh_token) are missing
* @throws {BadRequestException} - When the refresh token is missing
*/
async refreshToken(integrationId: ID): Promise<IZapierAccessTokens> {
try {
@@ -92,21 +91,18 @@ export class ZapierService {
throw new NotFoundException(`No settings found for integration ID ${integrationId}`);
}
// Extract required settings
const client_id = settings.find((s) => s.settingsName === 'client_id')?.settingsValue;
const client_secret = settings.find((s) => s.settingsName === 'client_secret')?.settingsValue;
const refresh_token = settings.find((s) => s.settingsName === 'refresh_token')?.settingsValue;
// Extract the Gauzy-issued OAuth refresh token
const refresh_token = settings.find((s) => s.settingsName === 'oauth_refresh_token')?.settingsValue;
if (!client_id || !client_secret || !refresh_token) {
this.logger.warn(`Missing required settings for integration ID ${integrationId}`);
throw new BadRequestException('Missing required Zapier integration settings');
if (!refresh_token) {
this.logger.warn(`Missing refresh token for integration ID ${integrationId}`);
throw new BadRequestException('Missing refresh token for integration');
}
const result = await this.generateAndStoreNewTokens(integrationId);
this.logger.log(`Successfully refreshed tokens for integration ID ${integrationId}`, {
integrationId,
client_id,
tenantId: settings[0]?.tenantId,
organizationId: settings[0]?.organizationId
});
@@ -135,7 +131,7 @@ export class ZapierService {
return await this._integrationSettingService.findOneByWhereOptions({
integration: { id: integrationId },
integrationId,
settingsName: 'access_token'
settingsName: 'zapier_access_token'
});
} catch (error) {
throw new NotFoundException(`Access token for integration ID ${integrationId} not found`);
@@ -181,7 +177,7 @@ export class ZapierService {
scope: ZAPIER_OAUTH_SCOPES
});
return `${ZAPIER_API_URL}/v2/authorize?${params.toString()}`;
return `${ZAPIER_BASE_URL}/oauth/authorize/?${params.toString()}`;
} catch (error) {
this.logger.error('Error generating Zapier authorization URL:', error);
throw new BadRequestException('Failed to generate authorization URL');
@@ -330,55 +326,49 @@ export class ZapierService {
/**
* Validates the state parameter without deleting it.
* This is used for initial validation before the OAuth flow.
* Searches across all tenants by state value so it works from @Public() endpoints.
*
* @param state The state parameter to validate
* @returns The parsed state data if valid
* @returns The parsed state data if valid (includes tenantId and integrationId)
* @throws {BadRequestException} If state is invalid or expired
*/
async parseAuthState(state: string): Promise<{ state: string; expiresAt?: string }> {
async parseAuthState(
state: string
): Promise<{ state: string; tenantId?: string; integrationId?: string; expiresAt?: string }> {
try {
// Basic validation - state should be a non-empty string
if (!state || typeof state !== 'string' || state.trim().length === 0) {
throw new BadRequestException('Invalid state parameter');
}
const tenantId = RequestContext.currentTenantId();
if (!tenantId) {
throw new BadRequestException('Tenant ID is required');
}
// Escape LIKE metacharacters (including backslash) to prevent unintended pattern matching
const escapedState = state.replace(/\\/g, '\\\\').replace(/[%_]/g, '\\$&');
// Find the integration by current tenant and Zapier provider
const integration = await this._integrationService.findOneByOptions({
// Search for the state across all tenants by value
const stateSettings = await this._integrationSettingService.find({
where: {
tenantId,
name: IntegrationEnum.ZAPIER
} as IIntegrationFilter,
relations: ['settings']
});
if (!integration) {
throw new NotFoundException('Zapier integration not found for current tenant');
}
// Get settings to retrieve state
const settings = await this._integrationSettingService.find({
where: {
integration: { id: integration.id }
settingsName: 'state',
settingsValue: Like(`%"state":"${escapedState}"%`)
}
});
const storedState = settings.find((s) => s.settingsName === 'state')?.settingsValue;
// Find the matching state setting
const stateSetting = stateSettings.find((s) => {
try {
const parsed = JSON.parse(s.settingsValue ?? '{}');
return parsed.state === state;
} catch {
return false;
}
});
if (!storedState) {
if (!stateSetting) {
throw new BadRequestException('State not found');
}
try {
const parsedState = JSON.parse(storedState);
if (parsedState.state !== state) {
throw new BadRequestException('Invalid state parameter');
}
const parsedState = JSON.parse(stateSetting.settingsValue);
// Check expiration
if (parsedState.expiresAt && new Date(parsedState.expiresAt) < new Date()) {
throw new BadRequestException('State has expired');
@@ -386,6 +376,9 @@ export class ZapierService {
return parsedState;
} catch (error) {
if (error instanceof BadRequestException) {
throw error;
}
throw new BadRequestException('Invalid state format');
}
} catch (error) {
@@ -410,7 +403,7 @@ export class ZapierService {
integrationId: string,
organizationId?: string
): Promise<void> {
const expirationTime = new Date();
let expirationTime = new Date();
expirationTime.setMinutes(expirationTime.getMinutes() + 10); // State expires in 10 minutes
await this._integrationSettingService.create({
@@ -420,25 +413,35 @@ export class ZapierService {
integrationId,
settingsValue: JSON.stringify({
state,
tenantId,
integrationId,
expiresAt: expirationTime.toISOString()
})
});
}
/**
* Store client credentials for later use in OAuth flow
* Store integration credentials using server-side config.
* Client credentials (client_id, client_secret) are read from environment variables
* and are never exposed to or stored in tenant records.
*/
async storeIntegrationCredentials(input: ICreateZapierIntegrationInput): Promise<IIntegrationTenant> {
async storeIntegrationCredentials(input: { organizationId: string; state: string }): Promise<IIntegrationTenant> {
const tenantId = RequestContext.currentTenantId();
if (!tenantId) {
throw new BadRequestException('Tenant ID is required');
}
const { client_id, client_secret, state, organizationId } = input;
const { state, organizationId } = input;
if (!state) {
throw new BadRequestException('State parameter is required');
}
// Validate that server-side OAuth credentials are configured (do NOT persist them to tenant)
const zapierConfig = this.config.get('zapier');
if (!zapierConfig?.clientId || !zapierConfig?.clientSecret) {
throw new BadRequestException('Zapier OAuth credentials are not configured on the server. Please set GAUZY_ZAPIER_CLIENT_ID and GAUZY_ZAPIER_CLIENT_SECRET environment variables.');
}
// Find or create the base integration
const baseIntegration =
(await this._integrationService.findOneByOptions({
@@ -466,7 +469,7 @@ export class ZapierService {
};
}) as IIntegrationEntitySetting[];
// Create the integration first
// Create the integration (without storing global OAuth credentials in tenant records)
const integration = await this._commandBus.execute(
new IntegrationTenantUpdateOrCreateCommand(
{
@@ -487,18 +490,6 @@ export class ZapierService {
settingsValue: 'true',
tenantId,
organizationId
},
{
settingsName: 'client_id',
settingsValue: client_id,
tenantId,
organizationId
},
{
settingsName: 'client_secret',
settingsValue: client_secret,
tenantId,
organizationId
}
]
}
@@ -512,23 +503,28 @@ export class ZapierService {
}
/**
* Complete the OAuth flow by exchanging the authorization code for tokens
* Complete the OAuth flow by exchanging the authorization code for tokens.
* Accepts tenantId as a parameter so it works from @Public() callback endpoints.
*
* @param code The authorization code from Zapier
* @param state The state parameter for CSRF validation
* @param tenantId The tenant ID (from parsed state or RequestContext)
*/
async completeOAuthFlow(code: string, state: string): Promise<IIntegrationTenant> {
async completeOAuthFlow(code: string, state: string, tenantId?: string): Promise<IIntegrationTenant> {
try {
// Get current tenant ID from request context
const tenantId = RequestContext.currentTenantId();
if (!tenantId) {
// Use provided tenantId or fall back to request context
const resolvedTenantId = tenantId || RequestContext.currentTenantId();
if (!resolvedTenantId) {
throw new BadRequestException('Tenant ID is required');
}
// Validate and atomically delete the state
await this.validateAndDeleteState(state, tenantId);
await this.validateAndDeleteState(state, resolvedTenantId);
// Find the integration by current tenant and Zapier provider
// Find the integration by tenant and Zapier provider
const integration = await this._integrationService.findOneByOptions({
where: {
tenantId,
tenantId: resolvedTenantId,
name: IntegrationEnum.ZAPIER
} as IIntegrationFilter,
relations: ['settings']
@@ -543,43 +539,44 @@ export class ZapierService {
name: IntegrationEnum.ZAPIER
};
// Get settings to retrieve client_id and client_secret
// Read client credentials from server-side config (env vars) — never from tenant data
const zapierConfig = this.config.get('zapier');
const client_id = zapierConfig?.clientId;
const client_secret = zapierConfig?.clientSecret;
if (!client_id || !client_secret) {
throw new BadRequestException('Zapier OAuth credentials are not configured on the server');
}
// Fetch settings for other purposes (not client credentials)
const settings = await this._integrationSettingService.find({
where: {
integration: { id: integrationTenant.id }
}
});
const client_id = settings.find((s) => s.settingsName === 'client_id')?.settingsValue;
const client_secret = settings.find((s) => s.settingsName === 'client_secret')?.settingsValue;
if (!client_id || !client_secret) {
throw new BadRequestException('Missing required Zapier integration settings');
}
const redirect_uri = this.config.get('zapier')?.redirectUri;
if (!redirect_uri) {
throw new Error('Zapier redirect URI is not configured');
}
// Exchange code for tokens
// Exchange code for tokens using application/x-www-form-urlencoded (OAuth2 standard)
const tokenParams = new URLSearchParams({
client_id,
client_secret,
code,
grant_type: 'authorization_code',
redirect_uri
});
const response = await firstValueFrom(
this._httpService
.post(
`${ZAPIER_BASE_URL}/oauth/token/`,
{
client_id,
client_secret,
code,
grant_type: 'authorization_code',
redirect_uri
},
{
headers: {
'Content-Type': 'application/json'
}
.post(`${ZAPIER_BASE_URL}/oauth/token/`, tokenParams.toString(), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json'
}
)
})
.pipe(
catchError((error: AxiosError) => {
this.logger.error(
@@ -592,28 +589,41 @@ export class ZapierService {
)
);
// Store the tokens using the existing settings' tenant and organization info
// Store the tokens — delete ALL old token settings (Zapier + Gauzy OAuth) first, then save new ones
const existingSetting = settings[0];
const allTokenSettingNames = [
'zapier_access_token',
'zapier_refresh_token',
'oauth_access_token',
'oauth_refresh_token'
];
const staleTokens = settings.filter((s) => allTokenSettingNames.includes(s.settingsName));
await Promise.all(
staleTokens
.map((s) => s.id)
.filter((id): id is string => id != null)
.map((id) => this._integrationSettingService.delete(id))
);
const filteredSettings = settings.filter((s) => !allTokenSettingNames.includes(s.settingsName));
const updatedSettings = [
...settings,
...filteredSettings,
{
settingsName: 'access_token',
settingsName: 'zapier_access_token',
settingsValue: response.access_token,
tenantId: existingSetting?.tenantId || tenantId,
tenantId: existingSetting?.tenantId || resolvedTenantId,
organizationId: existingSetting?.organizationId,
integrationId: integrationTenant.id
},
{
settingsName: 'refresh_token',
settingsName: 'zapier_refresh_token',
settingsValue: response.refresh_token,
tenantId: existingSetting?.tenantId || tenantId,
tenantId: existingSetting?.tenantId || resolvedTenantId,
organizationId: existingSetting?.organizationId,
integrationId: integrationTenant.id
}
].filter((setting, index, self) => {
// Keep only unique settings by name (avoiding duplicates)
return index === self.findIndex((s) => s.settingsName === setting.settingsName);
}) as DeepPartial<IIntegrationEntitySetting>;
] as any;
await this._integrationSettingService.save(updatedSettings);
@@ -636,10 +646,13 @@ export class ZapierService {
*/
async fetchTriggers(token: string): Promise<IZapierEndpoint[]> {
try {
const response = await this.fetchIntegration<{ triggers: IZapierEndpoint[] }>('triggers', token);
const response = await this.fetchIntegration<{ triggers: IZapierEndpoint[] }>(
`${ZAPIER_API_URL}/v2/triggers`,
token
);
return response.triggers;
} catch (error) {
console.error('Failed to fetch Zapier triggers:', error);
this.logger.error('Failed to fetch Zapier triggers:', error);
throw new Error('Unable to fetch triggers from Zapier');
}
}
@@ -653,14 +666,133 @@ export class ZapierService {
*/
async fetchActions(token: string): Promise<IZapierEndpoint[]> {
try {
const response = await this.fetchIntegration<{ actions: IZapierEndpoint[] }>('actions', token);
const response = await this.fetchIntegration<{ actions: IZapierEndpoint[] }>(
`${ZAPIER_API_URL}/v2/actions`,
token
);
return response.actions;
} catch (error) {
console.error('Failed to fetch Zapier actions:', error);
this.logger.error('Failed to fetch Zapier actions:', error);
throw new Error('Unable to fetch actions from Zapier');
}
}
/**
* Fetches the list of Zaps belonging to the authenticated user.
* See https://docs.zapier.com/powered-by-zapier/api-reference/zaps/get-zaps-[v2]
*
* @param {string} token - The access token for authentication with the Zapier API.
* @returns {Promise<IZapierZap[]>} - A promise that resolves to an array of Zaps.
*/
async fetchZaps(token: string): Promise<IZapierZap[]> {
try {
const response = await this.fetchIntegration<{ zaps: IZapierZap[] }>(
`${ZAPIER_API_URL}/v2/zaps`,
token
);
return response.zaps ?? [];
} catch (error) {
this.logger.error('Failed to fetch Zapier zaps:', error);
if (error instanceof HttpException) {
throw error;
}
throw new Error('Unable to fetch zaps from Zapier');
}
}
/**
* Creates a new Zap on behalf of the authenticated user.
* See https://docs.zapier.com/powered-by-zapier/api-reference/zaps/create-a-zap
*
* @param {IZapierCreateZapInput} input - The Zap creation payload (title and steps).
* @param {string} token - The access token for authentication with the Zapier API.
* @returns {Promise<IZapierZap>} - A promise that resolves to the created Zap.
*/
async createZap(input: IZapierCreateZapInput, token: string): Promise<IZapierZap> {
try {
const headers = { Authorization: `Bearer ${token}` };
return await firstValueFrom(
this._httpService.post<IZapierZap>(`${ZAPIER_API_URL}/v2/zaps`, input, { headers }).pipe(
catchError((error: AxiosError<any>) => {
if (!error.response) {
throw new HttpException(
{ message: error.message, error },
HttpStatus.INTERNAL_SERVER_ERROR
);
}
const response: AxiosResponse<any> = error.response;
throw new HttpException({ message: error.message, error }, response.status);
}),
map((response: AxiosResponse<IZapierZap>) => response.data)
)
);
} catch (error) {
this.logger.error('Failed to create Zapier zap:', error);
if (error instanceof HttpException) {
throw error;
}
throw new Error('Unable to create zap on Zapier');
}
}
/**
* Fetches publicly available Zap templates from Zapier.
* See https://docs.zapier.com/powered-by-zapier/api-reference/zap-templates/get-zap-templates
*
* Zap templates are a public endpoint and do not require an OAuth access token.
* Per the Zapier docs, the endpoint lives at /v1/zap-templates and requires the
* server-configured `client_id` to be passed as a query parameter.
* See https://docs.zapier.com/powered-by-zapier/api-reference/zap-templates/get-zap-templates
*
* @param {number} [limit] - Optional page size to forward to Zapier.
* @returns {Promise<IZapierZapTemplate[]>} - A promise that resolves to an array of Zap templates.
*/
async fetchZapTemplates(limit?: number): Promise<IZapierZapTemplate[]> {
const clientId = this.config.get('zapier')?.clientId;
if (!clientId) {
throw new BadRequestException(
'Zapier client_id is not configured on the server. Please set GAUZY_ZAPIER_CLIENT_ID.'
);
}
const params: Record<string, string> = { client_id: clientId };
if (limit !== undefined && Number.isFinite(limit)) {
params['limit'] = String(limit);
}
try {
const response = await firstValueFrom(
this._httpService
.get<{ zap_templates: IZapierZapTemplate[] }>(`${ZAPIER_API_URL}/v1/zap-templates`, {
params
})
.pipe(
catchError((error: AxiosError<any>) => {
if (!error.response) {
throw new HttpException(
{ message: error.message, error },
HttpStatus.INTERNAL_SERVER_ERROR
);
}
const response: AxiosResponse<any> = error.response;
throw new HttpException({ message: error.message, error }, response.status);
}),
map(
(response: AxiosResponse<{ zap_templates: IZapierZapTemplate[] }>) =>
response.data
)
)
);
return response.zap_templates ?? [];
} catch (error) {
this.logger.error('Failed to fetch Zapier zap templates:', error);
if (error instanceof HttpException) {
throw error;
}
throw new Error('Unable to fetch zap templates from Zapier');
}
}
/**
* Retrieves the Zapier integration tenant associated with the given access token.
* @param token - The OAuth access token to verify.
@@ -668,9 +800,9 @@ export class ZapierService {
* @throws NotFoundException if the token is invalid or no Zapier integration is found.
*/
async findIntegrationByToken(token: string): Promise<IIntegrationTenant> {
// 1) Lookup the access_token setting
// 1) Lookup the Gauzy-issued OAuth access token (separate from Zapier API tokens)
const setting = await this._integrationSettingService.findOneByWhereOptions({
settingsName: 'access_token',
settingsName: 'oauth_access_token',
settingsValue: token
});
@@ -698,30 +830,52 @@ export class ZapierService {
}
/**
* Find integration by client_id
* @param clientId - The OAuth client ID
* Resolve the tenant integration by searching for a Gauzy-issued authorization code.
* Auth codes are stored per-integration with tenantId, so this reliably maps
* the code to the correct tenant regardless of shared global client_id.
*
* @param code - The Gauzy-issued authorization code
* @returns The matching IIntegrationTenant
* @throws NotFoundException if no integration is found
* @throws NotFoundException if the code is invalid or expired
*/
async findIntegrationByClientId(clientId: string): Promise<IIntegrationTenant> {
// Find the client_id setting
const setting = await this._integrationSettingService.findOneByWhereOptions({
settingsName: 'client_id',
settingsValue: clientId
async findIntegrationByAuthCode(code: string): Promise<IIntegrationTenant> {
// Search for auth_code settings by value across all tenants
const codeSettings = await this._integrationSettingService.find({
where: {
settingsName: 'auth_code'
}
});
if (!setting?.integrationId) {
throw new NotFoundException('Invalid client ID');
const codeSetting = codeSettings.find((s) => {
try {
const parsed = JSON.parse(s.settingsValue ?? '{}');
return parsed.code === code;
} catch {
return false;
}
});
if (!codeSetting?.integrationId) {
throw new NotFoundException('Invalid authorization code');
}
// Load the integration tenant
const integrationTenant = await this._integrationService.findOneByIdString(setting.integrationId, {
// Verify the code hasn't expired
try {
const parsed = JSON.parse(codeSetting.settingsValue ?? '{}');
if (parsed.expiresAt && new Date(parsed.expiresAt) < new Date()) {
throw new BadRequestException('Authorization code has expired');
}
} catch (e) {
if (e instanceof BadRequestException) throw e;
}
const integrationTenant = await this._integrationService.findOneByIdString(codeSetting.integrationId, {
where: { name: IntegrationEnum.ZAPIER },
relations: ['settings']
});
if (!integrationTenant) {
throw new NotFoundException('Zapier integration not found for the provided client ID');
throw new NotFoundException('Zapier integration not found for the provided authorization code');
}
return {
@@ -731,21 +885,60 @@ export class ZapierService {
}
/**
* Validate client secret for OAuth flow
* @param integrationId - The integration ID
* @param clientSecret - The client secret to validate
* @throws BadRequestException if client secret is invalid
* Resolve the tenant integration by searching for a Gauzy-issued refresh token.
* Refresh tokens are stored per-integration with tenantId, so this reliably maps
* the token to the correct tenant regardless of shared global client_id.
*
* @param refreshToken - The Gauzy-issued refresh token
* @returns The matching IIntegrationTenant
* @throws NotFoundException if the token is invalid
*/
async validateClientSecret(integrationId: ID, clientSecret: string): Promise<void> {
const settings = await this._integrationSettingService.find({
async findIntegrationByGauzyRefreshToken(refreshToken: string): Promise<IIntegrationTenant> {
// Search for oauth_refresh_token settings by value across all tenants
const tokenSettings = await this._integrationSettingService.find({
where: {
integration: { id: integrationId }
settingsName: 'oauth_refresh_token'
}
});
const storedClientSecret = settings.find((s) => s.settingsName === 'client_secret')?.settingsValue;
const tokenSetting = tokenSettings.find((s) => s.settingsValue === refreshToken);
if (!storedClientSecret || storedClientSecret !== clientSecret) {
if (!tokenSetting?.integrationId) {
throw new NotFoundException('Invalid refresh token');
}
const integrationTenant = await this._integrationService.findOneByIdString(tokenSetting.integrationId, {
where: { name: IntegrationEnum.ZAPIER },
relations: ['settings']
});
if (!integrationTenant) {
throw new NotFoundException('Zapier integration not found for the provided refresh token');
}
return {
...integrationTenant,
name: IntegrationEnum.ZAPIER
};
}
/**
* Validate that the provided client_id and client_secret match the server-side
* Zapier OAuth configuration. This replaces the old approach of looking up
* credentials in tenant integration records.
*
* @param clientId - The client_id from the request
* @param clientSecret - The client_secret from the request
* @throws BadRequestException if credentials don't match server config
*/
validateServerClientCredentials(clientId: string, clientSecret: string): void {
const zapierConfig = this.config.get('zapier');
if (
!zapierConfig?.clientId ||
!zapierConfig?.clientSecret ||
zapierConfig.clientId !== clientId ||
zapierConfig.clientSecret !== clientSecret
) {
throw new BadRequestException('Invalid client credentials');
}
}
@@ -768,17 +961,17 @@ export class ZapierService {
const tenantId = existingSettings[0]?.tenantId;
const organizationId = existingSettings[0]?.organizationId;
// Prepare token settings
// Prepare Gauzy-issued OAuth token settings (distinct from Zapier API tokens)
const tokenSettings = [
{
settingsName: 'access_token',
settingsName: 'oauth_access_token',
settingsValue: accessToken,
tenantId,
organizationId,
integrationId
},
{
settingsName: 'refresh_token',
settingsName: 'oauth_refresh_token',
settingsValue: refreshToken,
tenantId,
organizationId,
@@ -786,9 +979,21 @@ export class ZapierService {
}
];
// Remove existing token settings and add new ones
// Delete only Gauzy OAuth token settings before saving new ones (preserve Zapier API tokens)
const oauthTokenSettingNames = [
'oauth_access_token',
'oauth_refresh_token'
];
const staleTokens = existingSettings.filter((setting) => oauthTokenSettingNames.includes(setting.settingsName));
await Promise.all(
staleTokens
.map((s) => s.id)
.filter((id): id is string => id != null)
.map((id) => this._integrationSettingService.delete(id))
);
const filteredExistingSettings = existingSettings.filter(
(setting) => !['access_token', 'refresh_token'].includes(setting.settingsName)
(setting) => !oauthTokenSettingNames.includes(setting.settingsName)
);
const allSettings = [...filteredExistingSettings, ...tokenSettings] as DeepPartial<IIntegrationEntitySetting>;
@@ -835,20 +1040,12 @@ export class ZapierService {
throw new NotFoundException(`No settings found for integration ID ${integrationId}`);
}
// Verify the refresh token matches
const storedRefreshToken = settings.find((s) => s.settingsName === 'refresh_token')?.settingsValue;
// Verify the Gauzy-issued OAuth refresh token matches
const storedRefreshToken = settings.find((s) => s.settingsName === 'oauth_refresh_token')?.settingsValue;
if (!storedRefreshToken || storedRefreshToken !== refreshToken) {
throw new BadRequestException('Invalid refresh token');
}
// Extract required settings
const client_id = settings.find((s) => s.settingsName === 'client_id')?.settingsValue;
const client_secret = settings.find((s) => s.settingsName === 'client_secret')?.settingsValue;
if (!client_id || !client_secret) {
throw new BadRequestException('Missing required Zapier integration settings');
}
return this.generateAndStoreNewTokens(integrationId);
} catch (error: any) {
this.logger.error(`Failed to refresh token for integration ID ${integrationId}`, {
@@ -862,6 +1059,125 @@ export class ZapierService {
}
}
/**
* Store a short-lived authorization code for the OAuth flow.
* The code is single-use and expires in 10 minutes.
*
* @param integrationId The integration ID
* @param code The authorization code
* @param redirectUri The redirect URI used in the authorization request
*/
async storeAuthCode(integrationId: ID, code: string, redirectUri: string): Promise<void> {
const existingSettings = await this._integrationSettingService.find({
where: { integration: { id: integrationId } }
});
const tenantId = existingSettings[0]?.tenantId;
const organizationId = existingSettings[0]?.organizationId;
// Invalidate any existing auth_code records for this integration
const existingAuthCodes = await this._integrationSettingService.find({
where: {
integration: { id: integrationId },
settingsName: 'auth_code'
}
});
await Promise.all(
existingAuthCodes
.map((s) => s.id)
.filter((id): id is string => id != null)
.map((id) => this._integrationSettingService.delete(id))
);
let expirationTime = new Date();
expirationTime.setMinutes(expirationTime.getMinutes() + 10);
await this._integrationSettingService.create({
settingsName: 'auth_code',
tenantId,
organizationId,
integrationId,
settingsValue: JSON.stringify({
code,
redirectUri,
expiresAt: expirationTime.toISOString()
})
});
}
/**
* Validate and consume an authorization code (single-use, atomic delete).
* Uses a conditional delete (id + original settingsValue) to prevent
* concurrent requests from both validating and minting tokens.
*
* @param integrationId The integration ID
* @param code The authorization code to validate
* @param redirectUri The redirect URI to verify against the stored one
* @throws BadRequestException if code is invalid, expired, already consumed, or redirect_uri mismatch
*/
async validateAndConsumeAuthCode(integrationId: ID, code: string, redirectUri: string): Promise<void> {
const codeSettings = await this._integrationSettingService.find({
where: {
integration: { id: integrationId },
settingsName: 'auth_code'
}
});
// Find matching auth code
const codeSetting = codeSettings.find((s) => {
try {
const parsed = JSON.parse(s.settingsValue ?? '{}');
return parsed.code === code;
} catch {
return false;
}
});
if (!codeSetting || !codeSetting.id) {
throw new BadRequestException('Invalid authorization code');
}
let parsedCode: any;
try {
parsedCode = JSON.parse(codeSetting.settingsValue ?? '{}');
} catch {
// Corrupted data — cleanup by ID only and fail
await this._integrationSettingService.typeOrmIntegrationSettingRepository.delete(codeSetting.id);
throw new BadRequestException('Corrupted authorization code data');
}
// Check expiration
if (parsedCode.expiresAt && new Date(parsedCode.expiresAt) < new Date()) {
// Use repository.delete() with FindOptionsWhere for atomic single-use guarantee
const result = await this._integrationSettingService.typeOrmIntegrationSettingRepository.delete({
id: codeSetting.id,
settingsValue: codeSetting.settingsValue
});
if (result.affected !== 1) {
throw new BadRequestException('Invalid authorization code');
}
throw new BadRequestException('Authorization code has expired');
}
// Atomic single-use delete: consume the code before any further validation to prevent probing.
// We match on both id AND the exact settingsValue to detect concurrent consumption.
// Use repository.delete() with FindOptionsWhere to ensure both predicates are applied.
const result = await this._integrationSettingService.typeOrmIntegrationSettingRepository.delete({
id: codeSetting.id,
settingsValue: codeSetting.settingsValue
});
if (result.affected !== 1) {
// Another concurrent request already consumed this code.
throw new BadRequestException('Invalid authorization code');
}
// Verify redirect_uri matches (code already consumed to prevent probing)
if (parsedCode.redirectUri && parsedCode.redirectUri !== redirectUri) {
throw new BadRequestException('Redirect URI mismatch');
}
}
/**
* Retrieves the Zapier integration settings for the current tenant.
*
@@ -905,23 +1221,21 @@ export class ZapierService {
const enabledSetting = integrationTenant.settings?.find(
(setting: IIntegrationSetting) => setting.settingsName === 'is_enabled'
);
const clientIdSetting = integrationTenant.settings?.find(
(setting: IIntegrationSetting) => setting.settingsName === 'client_id'
);
const clientSecretSetting = integrationTenant.settings?.find(
(setting: IIntegrationSetting) => setting.settingsName === 'client_secret'
);
const accessTokenSetting = integrationTenant.settings?.find(
(setting: IIntegrationSetting) => setting.settingsName === 'access_token'
(setting: IIntegrationSetting) => setting.settingsName === 'zapier_access_token'
);
const refreshTokenSetting = integrationTenant.settings?.find(
(setting: IIntegrationSetting) => setting.settingsName === 'refresh_token'
(setting: IIntegrationSetting) => setting.settingsName === 'zapier_refresh_token'
);
// Client credentials are server-side config, not tenant-specific
const zapierConfig = this.config.get('zapier');
const hasClientCredentials = !!(zapierConfig?.clientId && zapierConfig?.clientSecret);
// Map to sanitized DTO
return {
isEnabled: enabledSetting ? enabledSetting.settingsValue === 'true' : false,
hasClientCredentials: !!(clientIdSetting?.settingsValue && clientSecretSetting?.settingsValue),
hasClientCredentials,
hasAccessToken: !!accessTokenSetting?.settingsValue,
hasRefreshToken: !!refreshTokenSetting?.settingsValue
};
@@ -11,8 +11,6 @@ export interface IZapierAccessTokens {
}
export interface ICreateZapierIntegrationInput extends IBasePerTenantAndOrganizationEntityModel {
client_id: string;
client_secret: string;
code?: string;
redirect_uri?: string;
state?: string;
@@ -44,6 +44,7 @@ typings/
# Optional npm cache directory
.npm
.cache/
# Optional eslint cache
.eslintcache
@@ -0,0 +1,8 @@
build
.cache
.env
src
tsconfig.json
tsconfig.tsbuildinfo
*.ts
!*.d.ts
@@ -1,6 +1,6 @@
{
"name": "gauzy-zapier",
"version": "1.0.2",
"version": "1.0.3",
"description": "Zapier integration for Gauzy",
"main": "index.js",
"scripts": {
@@ -13,13 +13,13 @@
"clean": "rimraf lib dist"
},
"dependencies": {
"zapier-platform-core": "16.5.0"
"zapier-platform-core": "18.3.0"
},
"devDependencies": {
"@types/node": "^24.10.0",
"rimraf": "^3.0.2",
"typescript": "^5.9.3",
"zapier-platform-cli": "16.5.0"
"zapier-platform-cli": "18.3.0"
},
"private": true
}
@@ -7,9 +7,9 @@ const appName = process.env.APP_NAME || 'Gauzy';
export const authentication = {
/** Specifies OAuth2 as the authentication type */
type: 'oauth2',
/** Configuration for testing the authentication */
/** Configuration for testing the authentication — uses Zapier-specific endpoint */
test: {
url: `${process.env.API_BASE_URL}/api/auth/authenticated`,
url: `${process.env.API_BASE_URL}/api/integration/zapier/auth/test`,
method: 'GET',
headers: {
Authorization: 'Bearer {{bundle.authData.access_token}}'
@@ -20,18 +20,17 @@ export const authentication = {
connectionLabel: async (z: ZObject, bundle: Bundle) => {
try {
const response = await z.request({
url: `${process.env.API_BASE_URL}/api/user/me`,
url: `${process.env.API_BASE_URL}/api/integration/zapier/auth/me`,
headers: {
Authorization: `Bearer ${bundle.authData.access_token}`
}
});
// Format the connection label with user information
const userData = response.data;
if (userData?.name) {
return `${userData.name} - ${appName}`;
const data = response.data;
if (data?.name) {
return `${data.name} - ${appName}`;
}
if (userData?.email) {
return `${userData.email} - ${appName}`;
if (data?.email) {
return `${data.email} - ${appName}`;
}
return `${appName} Connection`;
@@ -56,7 +55,7 @@ export const authentication = {
/** Configuration for obtaining access token */
getAccessToken: {
method: 'POST',
url: `${process.env.API_BASE_URL}/api/integration/zapier/token`,
url: `${process.env.API_BASE_URL}/api/integration/zapier/oauth/token`,
body: {
code: '{{bundle.inputData.code}}',
client_id: '{{process.env.CLIENT_ID}}',
@@ -72,7 +71,7 @@ export const authentication = {
/** Configuration for refreshing access token */
refreshAccessToken: {
method: 'POST',
url: `${process.env.API_BASE_URL}/api/integration/zapier/refresh-token`,
url: `${process.env.API_BASE_URL}/api/integration/zapier/oauth/refresh-token`,
body: {
refresh_token: '{{bundle.authData.refresh_token}}',
client_id: '{{process.env.CLIENT_ID}}',
@@ -1,30 +1,81 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, throwError } from 'rxjs';
import { tap, catchError } from 'rxjs/operators';
import { IMakeComIntegrationSettings } from '@gauzy/contracts';
import {
IMakeComIntegrationSettings,
IMakeComOrganization,
IMakeComTeam,
IMakeComScenario,
IMakeComHook,
IMakeComConnection,
IMakeComTemplate,
IMakeComSetupStatus,
MakeComZone
} from '@gauzy/contracts';
import { MakeComService } from './make-com.service';
@Injectable({
providedIn: 'root'
})
export class MakeComStoreService {
private _settings$: BehaviorSubject<IMakeComIntegrationSettings | null> =
new BehaviorSubject<IMakeComIntegrationSettings | null>(null);
public settings$: Observable<IMakeComIntegrationSettings | null> = this._settings$.asObservable();
// ─── Setup status state ──────────────────────────────────────────────────
private readonly _setupStatus$ = new BehaviorSubject<IMakeComSetupStatus | null>(null);
public setupStatus$ = this._setupStatus$.asObservable();
private _oauthConfig$: BehaviorSubject<{ clientId: string; redirectUri: string } | null> = new BehaviorSubject<{
clientId: string;
redirectUri: string;
} | null>(null);
public oauthConfig$: Observable<{ clientId: string; redirectUri: string } | null> =
this._oauthConfig$.asObservable();
// ─── Settings state ─────────────────────────────────────────────────────
private readonly _settings$ = new BehaviorSubject<IMakeComIntegrationSettings | null>(null);
public settings$ = this._settings$.asObservable();
// ─── Zone state ─────────────────────────────────────────────────────────
private readonly _zone$ = new BehaviorSubject<MakeComZone | null>(null);
public zone$ = this._zone$.asObservable();
// ─── Make.com context state ─────────────────────────────────────────────
private readonly _makeOrganizations$ = new BehaviorSubject<IMakeComOrganization[]>([]);
public makeOrganizations$ = this._makeOrganizations$.asObservable();
private readonly _selectedMakeOrganization$ = new BehaviorSubject<IMakeComOrganization | null>(null);
public selectedMakeOrganization$ = this._selectedMakeOrganization$.asObservable();
private readonly _makeTeams$ = new BehaviorSubject<IMakeComTeam[]>([]);
public makeTeams$ = this._makeTeams$.asObservable();
private readonly _selectedMakeTeam$ = new BehaviorSubject<IMakeComTeam | null>(null);
public selectedMakeTeam$ = this._selectedMakeTeam$.asObservable();
// ─── Resource state ─────────────────────────────────────────────────────
private readonly _scenarios$ = new BehaviorSubject<IMakeComScenario[]>([]);
public scenarios$ = this._scenarios$.asObservable();
private readonly _hooks$ = new BehaviorSubject<IMakeComHook[]>([]);
public hooks$ = this._hooks$.asObservable();
private readonly _connections$ = new BehaviorSubject<IMakeComConnection[]>([]);
public connections$ = this._connections$.asObservable();
private readonly _templates$ = new BehaviorSubject<IMakeComTemplate[]>([]);
public templates$ = this._templates$.asObservable();
constructor(private readonly _makeComService: MakeComService) {}
/**
* Loads the current Make.com integration settings
* @returns An observable of the settings
*/
// ─── Setup Status ───────────────────────────────────────────────────────
loadSetupStatus(organizationId?: string): Observable<IMakeComSetupStatus> {
return this._makeComService.getSetupStatus(organizationId).pipe(
tap((status) => this._setupStatus$.next(status)),
catchError((error) => {
console.error('Error loading Make.com setup status:', error);
return throwError(() => error);
})
);
}
getSetupStatus(): IMakeComSetupStatus | null {
return this._setupStatus$.getValue();
}
// ─── Settings ───────────────────────────────────────────────────────────
loadIntegrationSettings(): Observable<IMakeComIntegrationSettings> {
return this._makeComService.getIntegrationSettings().pipe(
tap((settings) => this._settings$.next(settings)),
@@ -35,11 +86,6 @@ export class MakeComStoreService {
);
}
/**
* Updates the Make.com integration settings
* @param settings The updated settings
* @returns An observable of the updated settings
*/
updateIntegrationSettings(settings: {
isEnabled: boolean;
webhookUrl: string;
@@ -53,58 +99,218 @@ export class MakeComStoreService {
);
}
/**
* Adds or updates Make.com OAuth settings
* @param credentials The OAuth credentials
* @returns An observable of the updated settings
*/
addOAuthSettings(credentials: { clientId: string; clientSecret: string }): Observable<{
initializeIntegration(body: { organizationId: string }): Observable<{
authorizationUrl: string;
integrationId: string;
}> {
return this._makeComService.addOAuthSettings(credentials).pipe(
return this._makeComService.initializeIntegration(body).pipe(
catchError((error) => {
console.error('Error adding Make.com OAuth settings:', error);
console.error('Error initializing Make.com integration:', error);
return throwError(() => error);
})
);
}
/**
* Loads the OAuth configuration
* @returns An observable of the OAuth config
*/
loadOAuthConfig(): Observable<{ clientId: string; redirectUri: string }> {
return this._makeComService.getOAuthConfig().pipe(
tap((config) => this._oauthConfig$.next(config)),
catchError((error) => {
console.error('Error loading Make.com OAuth config:', error);
return throwError(() => error);
})
);
}
/**
* Gets the current integration settings without making an API call
* @returns The current settings or null if not loaded
*/
getCurrentSettings(): IMakeComIntegrationSettings | null {
return this._settings$.getValue();
}
/**
* Gets the current OAuth configuration without making an API call
* @returns The current OAuth config or null if not loaded
*/
getCurrentOAuthConfig(): { clientId: string; redirectUri: string } | null {
return this._oauthConfig$.getValue();
// ─── Zone ───────────────────────────────────────────────────────────────
loadZone(organizationId?: string): Observable<MakeComZone | null> {
return this._makeComService.getZone(organizationId).pipe(
tap((zone) => this._zone$.next(zone)),
catchError((error) => {
console.error('Error loading Make.com zone:', error);
return throwError(() => error);
})
);
}
/**
* Clears all stored data
*/
setZone(zone: MakeComZone, organizationId?: string): Observable<{ success: boolean; zone: MakeComZone }> {
return this._makeComService.setZone(zone, organizationId).pipe(
tap(() => {
this._zone$.next(zone);
this._setupStatus$.next(null);
this._selectedMakeOrganization$.next(null);
this._makeOrganizations$.next([]);
this._selectedMakeTeam$.next(null);
this._makeTeams$.next([]);
this._scenarios$.next([]);
this._hooks$.next([]);
this._connections$.next([]);
this._templates$.next([]);
}),
catchError((error) => {
console.error('Error setting Make.com zone:', error);
return throwError(() => error);
})
);
}
getZone(): MakeComZone | null {
return this._zone$.getValue();
}
// ─── Organizations ──────────────────────────────────────────────────────
loadMakeOrganizations(organizationId?: string): Observable<IMakeComOrganization[]> {
return this._makeComService.listOrganizations(organizationId).pipe(
tap((orgs) => this._makeOrganizations$.next(orgs)),
catchError((error) => {
console.error('Error loading Make.com organizations:', error);
return throwError(() => error);
})
);
}
selectMakeOrganization(org: IMakeComOrganization, organizationId?: string): Observable<any> {
return this._makeComService.setMakeOrganization(org.id, organizationId).pipe(
tap(() => {
this._selectedMakeOrganization$.next(org);
this._selectedMakeTeam$.next(null);
this._makeTeams$.next([]);
this._scenarios$.next([]);
this._hooks$.next([]);
this._connections$.next([]);
this._templates$.next([]);
}),
catchError((error) => {
console.error('Error selecting Make.com organization:', error);
return throwError(() => error);
})
);
}
// ─── Teams ──────────────────────────────────────────────────────────────
loadMakeTeams(makeOrgId?: number, organizationId?: string): Observable<IMakeComTeam[]> {
return this._makeComService.listTeams(makeOrgId, organizationId).pipe(
tap((teams) => this._makeTeams$.next(teams)),
catchError((error) => {
console.error('Error loading Make.com teams:', error);
return throwError(() => error);
})
);
}
selectMakeTeam(team: IMakeComTeam, organizationId?: string): Observable<any> {
return this._makeComService.setMakeTeam(team.id, organizationId).pipe(
tap(() => {
this._selectedMakeTeam$.next(team);
this._scenarios$.next([]);
this._hooks$.next([]);
this._connections$.next([]);
this._templates$.next([]);
}),
catchError((error) => {
console.error('Error selecting Make.com team:', error);
return throwError(() => error);
})
);
}
// ─── Scenarios ──────────────────────────────────────────────────────────
loadScenarios(teamId?: number, organizationId?: string): Observable<IMakeComScenario[]> {
return this._makeComService.listScenarios(teamId, organizationId).pipe(
tap((scenarios) => this._scenarios$.next(scenarios)),
catchError((error) => {
console.error('Error loading Make.com scenarios:', error);
return throwError(() => error);
})
);
}
startScenario(id: number, organizationId?: string): Observable<IMakeComScenario> {
return this._makeComService.startScenario(id, organizationId);
}
stopScenario(id: number, organizationId?: string): Observable<IMakeComScenario> {
return this._makeComService.stopScenario(id, organizationId);
}
runScenario(id: number, organizationId?: string): Observable<any> {
return this._makeComService.runScenario(id, organizationId);
}
deleteScenario(id: number, organizationId?: string): Observable<void> {
return this._makeComService.deleteScenario(id, organizationId);
}
// ─── Hooks ──────────────────────────────────────────────────────────────
loadHooks(teamId?: number, organizationId?: string): Observable<IMakeComHook[]> {
return this._makeComService.listHooks(teamId, organizationId).pipe(
tap((hooks) => this._hooks$.next(hooks)),
catchError((error) => {
console.error('Error loading Make.com hooks:', error);
return throwError(() => error);
})
);
}
enableHook(id: number, organizationId?: string): Observable<any> {
return this._makeComService.enableHook(id, organizationId);
}
disableHook(id: number, organizationId?: string): Observable<any> {
return this._makeComService.disableHook(id, organizationId);
}
pingHook(id: number, organizationId?: string): Observable<any> {
return this._makeComService.pingHook(id, organizationId);
}
deleteHook(id: number, organizationId?: string): Observable<void> {
return this._makeComService.deleteHook(id, organizationId);
}
// ─── Connections ────────────────────────────────────────────────────────
loadConnections(teamId?: number, organizationId?: string): Observable<IMakeComConnection[]> {
return this._makeComService.listConnections(teamId, organizationId).pipe(
tap((connections) => this._connections$.next(connections)),
catchError((error) => {
console.error('Error loading Make.com connections:', error);
return throwError(() => error);
})
);
}
testConnection(id: number, organizationId?: string): Observable<any> {
return this._makeComService.testConnection(id, organizationId);
}
deleteConnection(id: number, organizationId?: string): Observable<void> {
return this._makeComService.deleteConnection(id, organizationId);
}
// ─── Templates ──────────────────────────────────────────────────────────
loadTemplates(teamId?: number, organizationId?: string): Observable<IMakeComTemplate[]> {
return this._makeComService.listTemplates(teamId, organizationId).pipe(
tap((templates) => this._templates$.next(templates)),
catchError((error) => {
console.error('Error loading Make.com templates:', error);
return throwError(() => error);
})
);
}
// ─── Store Management ───────────────────────────────────────────────────
clearStore(): void {
this._setupStatus$.next(null);
this._settings$.next(null);
this._oauthConfig$.next(null);
this._zone$.next(null);
this._makeOrganizations$.next([]);
this._selectedMakeOrganization$.next(null);
this._makeTeams$.next([]);
this._selectedMakeTeam$.next(null);
this._scenarios$.next([]);
this._hooks$.next([]);
this._connections$.next([]);
this._templates$.next([]);
}
}
@@ -1,69 +1,59 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { API_PREFIX } from '@gauzy/ui-core/common';
import { IMakeComIntegrationSettings, IMakeComCreateIntegration, IMakeComOAuthTokenDTO } from '@gauzy/contracts';
import {
IMakeComIntegrationSettings,
IMakeComOAuthTokenDTO,
IMakeComOrganization,
IMakeComTeam,
IMakeComConnection,
IMakeComScenario,
IMakeComHook,
IMakeComTemplate,
IMakeComSetupStatus,
MakeComZone
} from '@gauzy/contracts';
@Injectable({
providedIn: 'root'
})
export class MakeComService {
private readonly API_URL = `${API_PREFIX}/integration/make-com`;
private readonly API_BASE = `${this.API_URL}/api`;
constructor(private readonly http: HttpClient) {}
/**
* Get Make.com integration settings for the current tenant
*/
// ─── Settings & Auth ────────────────────────────────────────────────────
getIntegrationSettings(): Observable<IMakeComIntegrationSettings> {
return this.http.get<IMakeComIntegrationSettings>(`${API_PREFIX}/integration/make-com`);
return this.http.get<IMakeComIntegrationSettings>(this.API_URL);
}
/**
* Update Make.com integration settings
*/
updateIntegrationSettings(settings: {
isEnabled: boolean;
webhookUrl: string;
}): Observable<IMakeComIntegrationSettings> {
return this.http.post<IMakeComIntegrationSettings>(`${API_PREFIX}/integration/make-com`, settings);
return this.http.post<IMakeComIntegrationSettings>(this.API_URL, settings);
}
/**
* Add or update Make.com OAuth settings
*/
addOAuthSettings(credentials: IMakeComCreateIntegration): Observable<{
initializeIntegration(body: { organizationId: string }): Observable<{
authorizationUrl: string;
integrationId: string;
}> {
return this.http.post<{
authorizationUrl: string;
integrationId: string;
}>(`${API_PREFIX}/integration/make-com/oauth-settings`, {
client_id: credentials.clientId,
client_secret: credentials.clientSecret
});
}>(`${this.API_URL}/oauth-settings`, body);
}
/**
* Get Make.com OAuth configuration
*/
getOAuthConfig(): Observable<{ clientId: string; redirectUri: string }> {
return this.http.get<{ clientId: string; redirectUri: string }>(
`${API_PREFIX}/integration/make-com/oauth-config`
);
}
/**
* Handle OAuth callback from Make.com
*/
handleOAuthCallback(code: string, state: string): Observable<IMakeComOAuthTokenDTO> {
return this.http.get<IMakeComOAuthTokenDTO>(`${API_PREFIX}/integration/make-com/oauth/callback`, {
return this.http.get<IMakeComOAuthTokenDTO>(`${this.API_URL}/oauth/callback`, {
params: { code, state }
});
}
/**
* Handle token requests from Make.com custom apps
*/
handleTokenRequest(body: {
grant_type: string;
code: string;
@@ -72,6 +62,190 @@ export class MakeComService {
client_secret: string;
redirect_uri: string;
}): Observable<IMakeComOAuthTokenDTO> {
return this.http.post<IMakeComOAuthTokenDTO>(`${API_PREFIX}/integration/make-com/token`, body);
return this.http.post<IMakeComOAuthTokenDTO>(`${this.API_URL}/token`, body);
}
// ─── Setup Status ───────────────────────────────────────────────────────
getSetupStatus(organizationId?: string): Observable<IMakeComSetupStatus> {
const params: any = {};
if (organizationId) params.organizationId = organizationId;
return this.http.get<IMakeComSetupStatus>(`${this.API_BASE}/setup-status`, { params });
}
// ─── Zone Configuration ─────────────────────────────────────────────────
getZone(organizationId?: string): Observable<MakeComZone | null> {
const params: any = {};
if (organizationId) params.organizationId = organizationId;
return this.http.get<{ zone: MakeComZone | null }>(`${this.API_BASE}/zone`, { params }).pipe(
map((res) => res.zone)
);
}
setZone(zone: MakeComZone, organizationId?: string): Observable<{ success: boolean; zone: MakeComZone }> {
return this.http.post<{ success: boolean; zone: MakeComZone }>(`${this.API_BASE}/zone`, {
zone,
organizationId
});
}
// ─── Context (Make.com org/team selection) ──────────────────────────────
setMakeOrganization(
makeOrganizationId: number,
organizationId?: string
): Observable<{ success: boolean; makeOrganizationId: number }> {
return this.http.post<{ success: boolean; makeOrganizationId: number }>(
`${this.API_BASE}/context/organization`,
{ makeOrganizationId, organizationId }
);
}
setMakeTeam(
makeTeamId: number,
organizationId?: string
): Observable<{ success: boolean; makeTeamId: number }> {
return this.http.post<{ success: boolean; makeTeamId: number }>(`${this.API_BASE}/context/team`, {
makeTeamId,
organizationId
});
}
// ─── Organizations ──────────────────────────────────────────────────────
listOrganizations(organizationId?: string): Observable<IMakeComOrganization[]> {
const params: any = {};
if (organizationId) params.organizationId = organizationId;
return this.http
.get<{ organizations: IMakeComOrganization[] }>(`${this.API_BASE}/organizations`, { params })
.pipe(map((res) => res.organizations));
}
// ─── Teams ──────────────────────────────────────────────────────────────
listTeams(makeOrgId?: number, organizationId?: string): Observable<IMakeComTeam[]> {
const params: any = {};
if (makeOrgId) params.makeOrgId = makeOrgId;
if (organizationId) params.organizationId = organizationId;
return this.http.get<{ teams: IMakeComTeam[] }>(`${this.API_BASE}/teams`, { params }).pipe(
map((res) => res.teams)
);
}
// ─── Connections ────────────────────────────────────────────────────────
listConnections(teamId?: number, organizationId?: string): Observable<IMakeComConnection[]> {
const params: any = {};
if (teamId) params.teamId = teamId;
if (organizationId) params.organizationId = organizationId;
return this.http
.get<{ connections: IMakeComConnection[] }>(`${this.API_BASE}/connections`, { params })
.pipe(map((res) => res.connections));
}
deleteConnection(id: number, organizationId?: string): Observable<void> {
const params: any = {};
if (organizationId) params.organizationId = organizationId;
return this.http.delete<void>(`${this.API_BASE}/connections/${id}`, { params });
}
testConnection(id: number, organizationId?: string): Observable<any> {
const params: any = {};
if (organizationId) params.organizationId = organizationId;
return this.http.post<any>(`${this.API_BASE}/connections/${id}/test`, null, { params });
}
// ─── Scenarios ──────────────────────────────────────────────────────────
listScenarios(teamId?: number, organizationId?: string): Observable<IMakeComScenario[]> {
const params: any = {};
if (teamId) params.teamId = teamId;
if (organizationId) params.organizationId = organizationId;
return this.http
.get<{ scenarios: IMakeComScenario[] }>(`${this.API_BASE}/scenarios`, { params })
.pipe(map((res) => res.scenarios));
}
getScenario(id: number, organizationId?: string): Observable<IMakeComScenario> {
const params: any = {};
if (organizationId) params.organizationId = organizationId;
return this.http
.get<{ scenario: IMakeComScenario }>(`${this.API_BASE}/scenarios/${id}`, { params })
.pipe(map((res) => res.scenario));
}
startScenario(id: number, organizationId?: string): Observable<IMakeComScenario> {
const params: any = {};
if (organizationId) params.organizationId = organizationId;
return this.http
.post<{ scenario: IMakeComScenario }>(`${this.API_BASE}/scenarios/${id}/start`, null, { params })
.pipe(map((res) => res.scenario));
}
stopScenario(id: number, organizationId?: string): Observable<IMakeComScenario> {
const params: any = {};
if (organizationId) params.organizationId = organizationId;
return this.http
.post<{ scenario: IMakeComScenario }>(`${this.API_BASE}/scenarios/${id}/stop`, null, { params })
.pipe(map((res) => res.scenario));
}
runScenario(id: number, organizationId?: string): Observable<any> {
const params: any = {};
if (organizationId) params.organizationId = organizationId;
return this.http.post<any>(`${this.API_BASE}/scenarios/${id}/run`, null, { params });
}
deleteScenario(id: number, organizationId?: string): Observable<void> {
const params: any = {};
if (organizationId) params.organizationId = organizationId;
return this.http.delete<void>(`${this.API_BASE}/scenarios/${id}`, { params });
}
// ─── Hooks (Webhooks) ───────────────────────────────────────────────────
listHooks(teamId?: number, organizationId?: string): Observable<IMakeComHook[]> {
const params: any = {};
if (teamId) params.teamId = teamId;
if (organizationId) params.organizationId = organizationId;
return this.http.get<{ hooks: IMakeComHook[] }>(`${this.API_BASE}/hooks`, { params }).pipe(
map((res) => res.hooks)
);
}
enableHook(id: number, organizationId?: string): Observable<any> {
const params: any = {};
if (organizationId) params.organizationId = organizationId;
return this.http.post<any>(`${this.API_BASE}/hooks/${id}/enable`, null, { params });
}
disableHook(id: number, organizationId?: string): Observable<any> {
const params: any = {};
if (organizationId) params.organizationId = organizationId;
return this.http.post<any>(`${this.API_BASE}/hooks/${id}/disable`, null, { params });
}
pingHook(id: number, organizationId?: string): Observable<any> {
const params: any = {};
if (organizationId) params.organizationId = organizationId;
return this.http.get<any>(`${this.API_BASE}/hooks/${id}/ping`, { params });
}
deleteHook(id: number, organizationId?: string): Observable<void> {
const params: any = {};
if (organizationId) params.organizationId = organizationId;
return this.http.delete<void>(`${this.API_BASE}/hooks/${id}`, { params });
}
// ─── Templates ──────────────────────────────────────────────────────────
listTemplates(teamId?: number, organizationId?: string): Observable<IMakeComTemplate[]> {
const params: any = {};
if (teamId) params.teamId = teamId;
if (organizationId) params.organizationId = organizationId;
return this.http
.get<{ templates: IMakeComTemplate[] }>(`${this.API_BASE}/templates`, { params })
.pipe(map((res) => res.templates));
}
}
@@ -5,12 +5,14 @@ import { API_PREFIX } from '@gauzy/ui-core/common';
import {
IZapierEndpoint,
IZapierOAuthTokenDTO,
ICreateZapierIntegrationInput,
IZapierWebhook,
IZapierCreateWebhookInput,
IZapierAuthConfig,
IZapierIntegrationSettings,
IIntegrationSetting,
IZapierZap,
IZapierCreateZapInput,
IZapierZapTemplate,
ID
} from '@gauzy/contracts';
@@ -80,9 +82,10 @@ export class ZapierService {
}
/**
* Initialize a new Zapier integration
* Initialize a new Zapier integration.
* No client credentials needed — server uses its own env-configured credentials.
*/
initializeIntegration(body: ICreateZapierIntegrationInput): Observable<{
initializeIntegration(body: { organizationId: string }): Observable<{
authorizationUrl: string;
integrationId: string;
}> {
@@ -97,7 +100,9 @@ export class ZapierService {
*/
getTriggers(token: string): Observable<IZapierEndpoint[]> {
return this.http.get<IZapierEndpoint[]>(`${API_PREFIX}/integration/zapier/triggers`, {
params: { token }
headers: {
Authorization: `Bearer ${token}`
}
});
}
@@ -106,7 +111,46 @@ export class ZapierService {
*/
getActions(token: string): Observable<IZapierEndpoint[]> {
return this.http.get<IZapierEndpoint[]>(`${API_PREFIX}/integration/zapier/actions`, {
params: { token }
headers: {
Authorization: `Bearer ${token}`
}
});
}
/**
* Get Zaps for the authenticated Zapier account
*/
getZaps(token: string): Observable<IZapierZap[]> {
return this.http.get<IZapierZap[]>(`${API_PREFIX}/integration/zapier/zaps`, {
headers: {
Authorization: `Bearer ${token}`
}
});
}
/**
* Create a new Zap on the authenticated Zapier account
*/
createZap(body: IZapierCreateZapInput, token: string): Observable<IZapierZap> {
return this.http.post<IZapierZap>(`${API_PREFIX}/integration/zapier/zaps`, body, {
headers: {
Authorization: `Bearer ${token}`
}
});
}
/**
* Get publicly available Zap templates.
* Zap templates do not require an OAuth access token — the server attaches
* the configured `client_id` on the way out to Zapier.
*/
getZapTemplates(limit?: number): Observable<IZapierZapTemplate[]> {
const params: Record<string, string> = {};
if (limit !== undefined) {
params['limit'] = String(limit);
}
return this.http.get<IZapierZapTemplate[]>(`${API_PREFIX}/integration/zapier/zap-templates`, {
params
});
}
@@ -120,7 +164,7 @@ export class ZapierService {
redirect_uri: string;
grant_type: string;
}): Observable<IZapierOAuthTokenDTO> {
return this.http.post<IZapierOAuthTokenDTO>(`${API_PREFIX}/integration/zapier/token`, body);
return this.http.post<IZapierOAuthTokenDTO>(`${API_PREFIX}/integration/zapier/oauth/token`, body);
}
/**
@@ -132,7 +176,7 @@ export class ZapierService {
client_secret: string;
grant_type: string;
}): Observable<IZapierOAuthTokenDTO> {
return this.http.post<IZapierOAuthTokenDTO>(`${API_PREFIX}/integration/zapier/refresh-token`, body);
return this.http.post<IZapierOAuthTokenDTO>(`${API_PREFIX}/integration/zapier/oauth/refresh-token`, body);
}
/**
+125 -12
View File
@@ -1189,16 +1189,93 @@
},
"MAKE_COM_PAGE": {
"SCENARIOS": "Scenarios",
"EXECUTIONS": "Executions",
"HISTORY": "History",
"WEBHOOKS": "Webhooks",
"WEBHOOKS_REFRESH": "Refresh webhooks",
"WEBHOOK_ENABLE": "Enable webhook",
"WEBHOOK_DISABLE": "Disable webhook",
"WEBHOOK_PING": "Ping webhook",
"SCENARIO_ACTIVE": "Active",
"SCENARIO_INACTIVE": "Inactive",
"SCENARIO_LAST_EDITED": "Last edited: {{value}}",
"SCENARIO_DISABLE": "Disable scenario",
"SCENARIO_ENABLE": "Enable scenario",
"SCENARIO_RUN_ONCE": "Run scenario once",
"WEBHOOK_ENABLED": "Enabled",
"WEBHOOK_DISABLED": "Disabled",
"WEBHOOK_TYPE": "Type",
"WEBHOOK_QUEUE": "Queue",
"TEMPLATE_PUBLIC": "Public",
"TEMPLATE_PUBLISHED": "Published",
"REFRESH_CONNECTIONS": "Refresh connections",
"CONFIGURATION_TITLE": "Make.com Configuration",
"ZONE_LABEL": "Zone",
"ZONE_HINT": "Select the zone that matches your Make.com account (e.g., us2.make.com)",
"ORGANIZATION_LABEL": "Make.com Organization",
"LOADING_ORGANIZATIONS": "Loading organizations...",
"NO_ORGANIZATIONS": "No organizations found",
"TEAM_LABEL": "Make.com Team",
"LOADING_TEAMS": "Loading teams...",
"NO_TEAMS": "No teams found",
"CONNECTED_TO": "Connected to {{org}} / {{team}} on {{zone}}",
"WEBHOOK_SETTINGS_TITLE": "Webhook Settings",
"CONNECTIONS": "Connections",
"TEMPLATES": "Templates",
"SETTINGS": "Settings",
"ERRORS": {
"INVALID_FORM": "Tim ber iyie pot buk ma mito ni ber",
"START_AUTHORIZATION": "Please start the authorization process"
"CONNECT_TO_MAKE_COM": "Connect to Make.com",
"CONNECT_INFO": "Click the button below to connect your Make.com account. You will be redirected to Make.com to authorize the integration.",
"NO_SCENARIOS": "No scenarios found. Create scenarios in your Make.com dashboard.",
"NO_HOOKS": "No webhooks found. Create webhooks in your Make.com dashboard.",
"NO_CONNECTIONS": "No connections found. Create connections in your Make.com dashboard.",
"NO_TEMPLATES": "No templates found.",
"TEST_CONNECTION": "Test connection",
"CONNECTION": {
"ACCOUNT": "Account: {{value}}",
"TYPE": "Type: {{value}}",
"PACKAGE": "Package: {{value}}",
"EXPIRES": "Expires: {{value}}"
},
"CLIENT_ID": "Client Id",
"CLIENT_SECRET": "Client Secret",
"NEXT_STEP_INFO": "I cokke ki authorization, ibi nyuto i Make.com me tiyo ki integration"
"ERRORS": {
"INVALID_FORM": "Please fill in all required fields",
"START_AUTHORIZATION": "Failed to start the authorization process",
"NO_ORGANIZATION": "Please select an organization first",
"LOAD_SETTINGS": "Failed to load Make.com settings",
"SAVE_SETTINGS": "Failed to save Make.com settings",
"LOAD_SETUP_STATUS": "Failed to load setup status",
"LOAD_SCENARIOS": "Failed to load scenarios",
"TOGGLE_SCENARIO": "Failed to toggle scenario",
"RUN_SCENARIO": "Failed to run scenario",
"LOAD_CONNECTIONS": "Failed to load connections",
"TEST_CONNECTION": "Failed to test connection",
"LOAD_WEBHOOKS": "Failed to load webhooks",
"TOGGLE_WEBHOOK": "Failed to toggle webhook",
"PING_WEBHOOK": "Failed to ping webhook",
"SET_ZONE": "Failed to set zone",
"LOAD_ORGANIZATIONS": "Failed to load organizations",
"SELECT_ORGANIZATION": "Failed to select organization",
"LOAD_TEAMS": "Failed to load teams",
"SELECT_TEAM": "Failed to select team",
"LOAD_TEMPLATES": "Failed to load templates",
"REQUEST_TIMEOUT": "Request timed out. Please try again."
},
"SUCCESS": {
"SETTINGS_SAVED": "Make.com settings saved successfully",
"SCENARIO_ACTIVATED": "Scenario activated",
"SCENARIO_DEACTIVATED": "Scenario deactivated",
"SCENARIO_EXECUTED": "Scenario executed",
"CONNECTION_VALID": "Connection \"{{name}}\" is valid",
"WEBHOOK_TOGGLED_ON": "Webhook enabled",
"WEBHOOK_TOGGLED_OFF": "Webhook disabled",
"WEBHOOK_PINGED": "Webhook \"{{name}}\" pinged successfully",
"ZONE_SET": "Zone set to {{zone}}",
"ORGANIZATION_SELECTED": "Organization \"{{name}}\" selected",
"TEAM_SELECTED": "Team \"{{name}}\" selected"
},
"SETUP_REQUIRED": {
"AUTHORIZE": "Please authorize with Make.com first. Go to the Settings tab to connect your account.",
"ZONE": "Please select your Make.com zone in the Settings tab.",
"ORGANIZATION": "Please select your Make.com organization in the Settings tab.",
"TEAM": "Please select your Make.com team in the Settings tab."
}
},
"MAKE_COM": {
"SETTINGS": {
@@ -1211,19 +1288,24 @@
}
},
"ZAPIER_PAGE": {
"CLIENT_ID": "Ket Zapier Client ID ni",
"CLIENT_SECRET": "Ket Zapier Client Secret ni",
"NEXT_STEP_INFO": "I cokke ki authorization, ibi nyuto i Zapier me tiyo ki integration",
"CONNECT_TO_ZAPIER": "Kub ki Zapier",
"CONNECT_INFO": "Dii gin ame piny me kube akaunt mamegi me Zapier. Ibi nyuto i Zapier me ye kwede integration.",
"ERRORS": {
"LOAD_SETTINGS": "Peko oo pe gonyo ter pa Zapier",
"SAVE_SETTINGS": "Peko oo pe gam ter pa Zapier",
"INVALID_FORM": "Tim ber iyie pot buk ma mito ni ber",
"MISSING_CREDENTIALS": "Peko pe tye lok ma loyo client",
"NO_ORGANIZATION": "Tim ber i yer lawoti mukwongo",
"NO_ACCESS_TOKEN": "Peke pe tye token ma loyo",
"LOAD_WEBHOOKS": "Peko oo pe gonyo webhooks",
"DELETE_WEBHOOK": "Peko oo pe junk webhook",
"LOAD_ACTIONS": "Peko oo pe gonyo tic",
"LOAD_TRIGGERS": "Peko oo pe gonyo lok ma gonyo",
"LOAD_ZAPS": "Peko oo pe gonyo zaps",
"LOAD_ZAP_TEMPLATES": "Peko oo pe gonyo template pa zap",
"CREATE_ZAP": "Peko oo pe create zap",
"ZAP_TITLE_REQUIRED": "Tim ber i miyo title me zap manyen",
"NO_INTEGRATION_ID": "Pe ki miyo ID me integration pa Zapier",
"START_AUTHORIZATION": "Ket cwoki pa authorization ni",
"INVALID_CALLBACK": "Peko tye callback ma tye maber",
"AUTHORIZATION": "Peko tye authorization ma tye maber",
@@ -1233,6 +1315,7 @@
"SUCCESS": {
"SETTINGS_SAVED": "Ter pa Zapier ogam ki ber",
"DELETE_WEBHOOK": "Webhook ojunk ki ber",
"CREATE_ZAP": "Ocreate zap ki ber",
"AUTHORIZATION": "Authorization puched ki ber"
},
"TAB": {
@@ -1275,6 +1358,26 @@
"WEBHOOKS": {
"TITLE": "Webhooks",
"NO_WEBHOOKS": "Pe tye webhook ma kigoyo"
},
"ZAPS": {
"TITLE": "Zaps",
"NO_ZAPS": "Pe tye zaps",
"CREATE": "Lok Zap",
"SUBMIT": "Lok",
"ADD_STEP": "Med step",
"OPEN_EDITOR": "Yab i Zapier",
"ENABLED": "Cako",
"DISABLED": "Juko",
"TITLE_PLACEHOLDER": "Ket title me Zap manyen",
"FIELDS": {
"TITLE": "Title",
"STEPS": "Steps",
"STEP": "Step",
"STEP_TITLE": "Step title",
"ACTION": "Tic",
"AUTHENTICATION": "Authentication",
"INPUTS": "Inputs (JSON)"
}
}
},
"ACTIVEPIECES_PAGE": {
@@ -1503,7 +1606,17 @@
"FILTER_INTEGRATIONS": "crwdns8270:0crwdne8270:0",
"SEARCH_INTEGRATIONS": "crwdns8272:0crwdne8272:0",
"PAID": "crwdns11205:0crwdne11205:0",
"INTEGRATION": "crwdns11207:0crwdne11207:0"
"INTEGRATION": "crwdns11207:0crwdne11207:0",
"MAKE_PAGE": {
"PROCESSING_AUTHORIZATION": "Processing Make.com authorization...",
"ERRORS": {
"AUTHORIZATION_FAILED": "Make.com authorization failed",
"INVALID_CALLBACK": "Invalid callback parameters"
},
"SUCCESS": {
"AUTHORIZATION_COMPLETE": "Make.com authorization completed successfully"
}
}
},
"DASHBOARD_PAGE": {
"ACCOUNTING": "crwdns3190:0crwdne3190:0",
+127 -14
View File
@@ -1293,16 +1293,93 @@
},
"MAKE_COM_PAGE": {
"SCENARIOS": "سيناريوهات",
"EXECUTIONS": "تنفيذات",
"HISTORY": "السجل",
"SETTINGS": "الإعدادات",
"ERRORS": {
"INVALID_FORM": "يرجى إدخال جميع الحقول المطلوبة",
"START_AUTHORIZATION": "يرجى بدء عملية التفويض"
"WEBHOOKS": "الويب هوكس",
"WEBHOOKS_REFRESH": "تحديث الويب هوكس",
"WEBHOOK_ENABLE": "تفعيل الويب هوك",
"WEBHOOK_DISABLE": "تعطيل الويب هوك",
"WEBHOOK_PING": "اختبار الويب هوك",
"SCENARIO_ACTIVE": "نشط",
"SCENARIO_INACTIVE": "غير نشط",
"SCENARIO_LAST_EDITED": "آخر تعديل: {{value}}",
"SCENARIO_DISABLE": "تعطيل السيناريو",
"SCENARIO_ENABLE": "تفعيل السيناريو",
"SCENARIO_RUN_ONCE": "تشغيل السيناريو مرة واحدة",
"WEBHOOK_ENABLED": "مفعّل",
"WEBHOOK_DISABLED": "معطّل",
"WEBHOOK_TYPE": "النوع",
"WEBHOOK_QUEUE": "قائمة الانتظار",
"TEMPLATE_PUBLIC": "عام",
"TEMPLATE_PUBLISHED": "منشور",
"REFRESH_CONNECTIONS": "تحديث الاتصالات",
"CONFIGURATION_TITLE": "إعدادات Make.com",
"ZONE_LABEL": "المنطقة",
"ZONE_HINT": "اختر المنطقة التي تطابق حساب Make.com الخاص بك (مثال: us2.make.com)",
"ORGANIZATION_LABEL": "منظمة Make.com",
"LOADING_ORGANIZATIONS": "جاري تحميل المنظمات...",
"NO_ORGANIZATIONS": "لم يتم العثور على منظمات",
"TEAM_LABEL": "فريق Make.com",
"LOADING_TEAMS": "جاري تحميل الفرق...",
"NO_TEAMS": "لم يتم العثور على فرق",
"CONNECTED_TO": "متصل بـ {{org}} / {{team}} على {{zone}}",
"WEBHOOK_SETTINGS_TITLE": "إعدادات الويب هوك",
"CONNECTIONS": "اتصالات",
"TEMPLATES": "قوالب",
"SETTINGS": "إعدادات",
"CONNECT_TO_MAKE_COM": "الاتصال بـ Make.com",
"CONNECT_INFO": "انقر على الزر أدناه لربط حساب Make.com الخاص بك. سيتم إعادة توجيهك إلى Make.com لتفويض التكامل.",
"NO_SCENARIOS": "لم يتم العثور على سيناريوهات. أنشئ سيناريوهات في لوحة تحكم Make.com.",
"NO_HOOKS": "لم يتم العثور على webhooks. أنشئ webhooks في لوحة تحكم Make.com.",
"NO_CONNECTIONS": "لم يتم العثور على اتصالات. أنشئ اتصالات في لوحة تحكم Make.com.",
"NO_TEMPLATES": "لم يتم العثور على قوالب.",
"TEST_CONNECTION": "اختبار الاتصال",
"CONNECTION": {
"ACCOUNT": "الحساب: {{value}}",
"TYPE": "النوع: {{value}}",
"PACKAGE": "الحزمة: {{value}}",
"EXPIRES": "ينتهي: {{value}}"
},
"CLIENT_ID": "معرف العميل",
"CLIENT_SECRET": "الرمز السري للعميل",
"NEXT_STEP_INFO": "بعد التفويض، سيتم توجيهك إلى Make.com لإكمال إعداد التكامل"
"ERRORS": {
"INVALID_FORM": "يرجى ملء جميع الحقول المطلوبة",
"START_AUTHORIZATION": "فشل في بدء عملية التفويض",
"NO_ORGANIZATION": "يرجى اختيار منظمة أولاً",
"LOAD_SETTINGS": "فشل في تحميل إعدادات Make.com",
"SAVE_SETTINGS": "فشل في حفظ إعدادات Make.com",
"LOAD_SETUP_STATUS": "فشل في تحميل حالة الإعداد",
"LOAD_SCENARIOS": "فشل في تحميل السيناريوهات",
"TOGGLE_SCENARIO": "فشل في تبديل السيناريو",
"RUN_SCENARIO": "فشل في تشغيل السيناريو",
"LOAD_CONNECTIONS": "فشل في تحميل الاتصالات",
"TEST_CONNECTION": "فشل في اختبار الاتصال",
"LOAD_WEBHOOKS": "فشل في تحميل الويب هوكس",
"TOGGLE_WEBHOOK": "فشل في تبديل الويب هوك",
"PING_WEBHOOK": "فشل في اختبار الويب هوك",
"SET_ZONE": "فشل في تعيين المنطقة",
"LOAD_ORGANIZATIONS": "فشل في تحميل المنظمات",
"SELECT_ORGANIZATION": "فشل في اختيار المنظمة",
"LOAD_TEAMS": "فشل في تحميل الفرق",
"SELECT_TEAM": "فشل في اختيار الفريق",
"LOAD_TEMPLATES": "فشل في تحميل القوالب",
"REQUEST_TIMEOUT": "انتهت مهلة الطلب. يرجى المحاولة مرة أخرى."
},
"SUCCESS": {
"SETTINGS_SAVED": "تم حفظ إعدادات Make.com بنجاح",
"SCENARIO_ACTIVATED": "تم تفعيل السيناريو",
"SCENARIO_DEACTIVATED": "تم تعطيل السيناريو",
"SCENARIO_EXECUTED": "تم تنفيذ السيناريو",
"CONNECTION_VALID": "الاتصال \"{{name}}\" صالح",
"WEBHOOK_TOGGLED_ON": "تم تمكين الويب هوك",
"WEBHOOK_TOGGLED_OFF": "تم تعطيل الويب هوك",
"WEBHOOK_PINGED": "تم اختبار الويب هوك \"{{name}}\" بنجاح",
"ZONE_SET": "تم تعيين المنطقة إلى {{zone}}",
"ORGANIZATION_SELECTED": "تم اختيار المنظمة \"{{name}}\"",
"TEAM_SELECTED": "تم اختيار الفريق \"{{name}}\""
},
"SETUP_REQUIRED": {
"AUTHORIZE": "يرجى تفويض Make.com أولاً. انتقل إلى علامة التبويب الإعدادات لربط حسابك.",
"ZONE": "يرجى اختيار منطقة Make.com في علامة التبويب الإعدادات.",
"ORGANIZATION": "يرجى اختيار منظمة Make.com في علامة التبويب الإعدادات.",
"TEAM": "يرجى اختيار فريق Make.com في علامة التبويب الإعدادات."
}
},
"MAKE_COM": {
"SETTINGS": {
@@ -1315,26 +1392,32 @@
}
},
"ZAPIER_PAGE": {
"CLIENT_ID": "معرف العميل",
"CLIENT_SECRET": "الرمز السري للعميل",
"NEXT_STEP_INFO": "بعد التفويض، سيتم توجيهك إلى Zapier لإكمال إعداد التكامل",
"CONNECT_TO_ZAPIER": "الاتصال بـ Zapier",
"CONNECT_INFO": "انقر على الزر أدناه لربط حسابك في Zapier. سيتم توجيهك إلى Zapier للتفويض بالتكامل.",
"ERRORS": {
"LOAD_SETTINGS": "فشل تحميل إعدادات Zapier",
"SAVE_SETTINGS": "فشل حفظ إعدادات Zapier",
"INVALID_FORM": "يرجى إدخال جميع الحقول المطلوبة",
"MISSING_CREDENTIALS": "بيانات الاعتماد للعميل مفقودة",
"NO_ORGANIZATION": "يرجى اختيار منظمة أولاً",
"NO_ACCESS_TOKEN": "لا يوجد رمز وصول متاح",
"LOAD_WEBHOOKS": "فشل تحميل ويب هوكس",
"DELETE_WEBHOOK": "فشل حذف ويب هوك",
"LOAD_ACTIONS": "فشل تحميل الإجراءات",
"LOAD_TRIGGERS": "فشل تحميل المحفزات",
"LOAD_ZAPS": "فشل تحميل zaps",
"LOAD_ZAP_TEMPLATES": "فشل تحميل قوالب zap",
"CREATE_ZAP": "فشل إنشاء zap",
"ZAP_TITLE_REQUIRED": "يرجى تقديم عنوان لـ zap الجديد",
"NO_INTEGRATION_ID": "لم يتم تقديم معرف تكامل Zapier",
"START_AUTHORIZATION": "يرجى بدء عملية التفويض",
"INVALID_TOKEN": "تم توفير رمز غير صالح",
"TOKEN_NOT_FOUND": "لم يتم العثور على الرمز"
},
"SUCCESS": {
"SETTINGS_SAVED": "تم حفظ إعدادات Zapier بنجاح",
"DELETE_WEBHOOK": "تم حذف ويب هوك بنجاح"
"DELETE_WEBHOOK": "تم حذف ويب هوك بنجاح",
"CREATE_ZAP": "تم إنشاء Zap بنجاح"
},
"TAB": {
"TRIGGERS": "المحفزات",
@@ -1376,6 +1459,26 @@
"WEBHOOKS": {
"TITLE": "ويب هوكس",
"NO_WEBHOOKS": "لا يوجد ويب هوكس مضبوط"
},
"ZAPS": {
"TITLE": "زابز",
"NO_ZAPS": "لا توجد زابز متاحة",
"CREATE": "إنشاء زاب",
"SUBMIT": "إنشاء",
"ADD_STEP": "إضافة خطوة",
"OPEN_EDITOR": "فتح في زابير",
"ENABLED": "مفعّل",
"DISABLED": "معطّل",
"TITLE_PLACEHOLDER": "أدخل عنواناً للزاب الجديد",
"FIELDS": {
"TITLE": "العنوان",
"STEPS": "الخطوات",
"STEP": "خطوة",
"STEP_TITLE": "عنوان الخطوة",
"ACTION": "الإجراء",
"AUTHENTICATION": "المصادقة",
"INPUTS": "المدخلات (JSON)"
}
}
},
"GAUZY_AI_PAGE": {
@@ -1608,7 +1711,17 @@
"FILTER_INTEGRATIONS": "ترشيح التكاملات",
"SEARCH_INTEGRATIONS": "البحث في التكاملات",
"PAID": "مدفوع",
"INTEGRATION": "التكامل"
"INTEGRATION": "التكامل",
"MAKE_PAGE": {
"PROCESSING_AUTHORIZATION": "جاري معالجة تفويض Make.com...",
"ERRORS": {
"AUTHORIZATION_FAILED": "فشل تفويض Make.com",
"INVALID_CALLBACK": "معلمات رد الاتصال غير صالحة"
},
"SUCCESS": {
"AUTHORIZATION_COMPLETE": "تم إكمال تفويض Make.com بنجاح"
}
}
},
"DASHBOARD_PAGE": {
"ACCOUNTING": "المحاسبة",
+124 -11
View File
@@ -1326,16 +1326,93 @@
},
"MAKE_COM_PAGE": {
"SCENARIOS": "Сценарии",
"EXECUTIONS": "Изпълнения",
"HISTORY": "История",
"WEBHOOKS": "Уебхукове",
"WEBHOOKS_REFRESH": "Опресняване на уебхукове",
"WEBHOOK_ENABLE": "Активиране на уебхук",
"WEBHOOK_DISABLE": "Деактивиране на уебхук",
"WEBHOOK_PING": "Тест на уебхук",
"SCENARIO_ACTIVE": "Активен",
"SCENARIO_INACTIVE": "Неактивен",
"SCENARIO_LAST_EDITED": "Последна редакция: {{value}}",
"SCENARIO_DISABLE": "Деактивиране на сценарий",
"SCENARIO_ENABLE": "Активиране на сценарий",
"SCENARIO_RUN_ONCE": "Еднократно изпълнение на сценарий",
"WEBHOOK_ENABLED": "Активиран",
"WEBHOOK_DISABLED": "Деактивиран",
"WEBHOOK_TYPE": "Тип",
"WEBHOOK_QUEUE": "Опашка",
"TEMPLATE_PUBLIC": "Публичен",
"TEMPLATE_PUBLISHED": "Публикуван",
"REFRESH_CONNECTIONS": "Опресняване на връзките",
"CONFIGURATION_TITLE": "Make.com Конфигурация",
"ZONE_LABEL": "Зона",
"ZONE_HINT": "Изберете зоната, която съвпада с вашия Make.com акаунт (напр. us2.make.com)",
"ORGANIZATION_LABEL": "Make.com Организация",
"LOADING_ORGANIZATIONS": "Зареждане на организации...",
"NO_ORGANIZATIONS": "Няма намерени организации",
"TEAM_LABEL": "Make.com Екип",
"LOADING_TEAMS": "Зареждане на екипи...",
"NO_TEAMS": "Няма намерени екипи",
"CONNECTED_TO": "Свързан с {{org}} / {{team}} в {{zone}}",
"WEBHOOK_SETTINGS_TITLE": "Настройки на уебхук",
"CONNECTIONS": "Връзки",
"TEMPLATES": "Шаблони",
"SETTINGS": "Настройки",
"CONNECT_TO_MAKE_COM": "Свързване с Make.com",
"CONNECT_INFO": "Кликнете бутона по-долу, за да свържете акаунта си в Make.com. Ще бъдете пренасочени към Make.com за оторизация на интеграцията.",
"NO_SCENARIOS": "Няма намерени сценарии. Създайте сценарии в таблото на Make.com.",
"NO_HOOKS": "Няма намерени уебхукове. Създайте уебхукове в таблото на Make.com.",
"NO_CONNECTIONS": "Няма намерени връзки. Създайте връзки в таблото на Make.com.",
"NO_TEMPLATES": "Няма намерени шаблони.",
"TEST_CONNECTION": "Тест на връзката",
"CONNECTION": {
"ACCOUNT": "Акаунт: {{value}}",
"TYPE": "Тип: {{value}}",
"PACKAGE": "Пакет: {{value}}",
"EXPIRES": "Изтича: {{value}}"
},
"ERRORS": {
"INVALID_FORM": "Моля, попълнете всички задължителни полета",
"START_AUTHORIZATION": "Моля, започнете процеса на оторизация"
"START_AUTHORIZATION": "Неуспешно стартиране на процеса на оторизация",
"NO_ORGANIZATION": "Моля, първо изберете организация",
"LOAD_SETTINGS": "Неуспешно зареждане на настройките на Make.com",
"SAVE_SETTINGS": "Неуспешно запазване на настройките на Make.com",
"LOAD_SETUP_STATUS": "Неуспешно зареждане на състоянието на настройката",
"LOAD_SCENARIOS": "Неуспешно зареждане на сценарии",
"TOGGLE_SCENARIO": "Неуспешно превключване на сценарий",
"RUN_SCENARIO": "Неуспешно изпълнение на сценарий",
"LOAD_CONNECTIONS": "Неуспешно зареждане на връзки",
"TEST_CONNECTION": "Неуспешно тестване на връзка",
"LOAD_WEBHOOKS": "Неуспешно зареждане на уебхукове",
"TOGGLE_WEBHOOK": "Неуспешно превключване на уебхук",
"PING_WEBHOOK": "Неуспешно тестване на уебхук",
"SET_ZONE": "Неуспешно задаване на зона",
"LOAD_ORGANIZATIONS": "Неуспешно зареждане на организации",
"SELECT_ORGANIZATION": "Неуспешно избиране на организация",
"LOAD_TEAMS": "Неуспешно зареждане на екипи",
"SELECT_TEAM": "Неуспешно избиране на екип",
"LOAD_TEMPLATES": "Неуспешно зареждане на шаблони",
"REQUEST_TIMEOUT": "Времето за заявка изтече. Моля, опитайте отново."
},
"CLIENT_ID": "ID на клиент",
"CLIENT_SECRET": "Тайна на клиент",
"NEXT_STEP_INFO": "След оторизацията ще бъдете пренасочени към Make.com, за да завършите настройката на интеграцията"
"SUCCESS": {
"SETTINGS_SAVED": "Настройките на Make.com са запазени успешно",
"SCENARIO_ACTIVATED": "Сценарият е активиран",
"SCENARIO_DEACTIVATED": "Сценарият е деактивиран",
"SCENARIO_EXECUTED": "Сценарият е изпълнен",
"CONNECTION_VALID": "Връзката \"{{name}}\" е валидна",
"WEBHOOK_TOGGLED_ON": "Уебхукът е активиран",
"WEBHOOK_TOGGLED_OFF": "Уебхукът е деактивиран",
"WEBHOOK_PINGED": "Уебхук \"{{name}}\" е тестван успешно",
"ZONE_SET": "Зоната е зададена на {{zone}}",
"ORGANIZATION_SELECTED": "Организация \"{{name}}\" е избрана",
"TEAM_SELECTED": "Екип \"{{name}}\" е избран"
},
"SETUP_REQUIRED": {
"AUTHORIZE": "Моля, първо оторизирайте Make.com. Отидете в раздел Настройки, за да свържете акаунта си.",
"ZONE": "Моля, изберете вашата зона Make.com в раздел Настройки.",
"ORGANIZATION": "Моля, изберете вашата организация Make.com в раздел Настройки.",
"TEAM": "Моля, изберете вашия екип Make.com в раздел Настройки."
}
},
"MAKE_COM": {
"SETTINGS": {
@@ -1348,26 +1425,32 @@
}
},
"ZAPIER_PAGE": {
"CLIENT_ID": "ID на клиент",
"CLIENT_SECRET": "Тайна на клиент",
"NEXT_STEP_INFO": "След оторизацията ще бъдете пренасочени към Zapier, за да завършите настройката на интеграцията",
"CONNECT_TO_ZAPIER": "Свързване със Zapier",
"CONNECT_INFO": "Натиснете бутона по-долу, за да свържете вашия акаунт в Zapier. Ще бъдете пренасочени към Zapier за оторизация на интеграцията.",
"ERRORS": {
"LOAD_SETTINGS": "Неуспешно зареждане на настройките на Zapier",
"SAVE_SETTINGS": "Неуспешно запазване на настройките на Zapier",
"INVALID_FORM": "Моля, попълнете всички задължителни полета",
"MISSING_CREDENTIALS": "Липсват идентификационни данни на клиента",
"NO_ORGANIZATION": "Моля, първо изберете организация",
"NO_ACCESS_TOKEN": "Няма достъпен токен за достъп",
"LOAD_WEBHOOKS": "Неуспешно зареждане на уебхукове",
"DELETE_WEBHOOK": "Неуспешно изтриване на уебхук",
"LOAD_ACTIONS": "Неуспешно зареждане на действия",
"LOAD_TRIGGERS": "Неуспешно зареждане на тригери",
"LOAD_ZAPS": "Неуспешно зареждане на zap-ове",
"LOAD_ZAP_TEMPLATES": "Неуспешно зареждане на шаблони за zap",
"CREATE_ZAP": "Неуспешно създаване на zap",
"ZAP_TITLE_REQUIRED": "Моля, предоставете заглавие за новия zap",
"NO_INTEGRATION_ID": "Не е предоставен идентификатор за интеграция на Zapier",
"START_AUTHORIZATION": "Моля, започнете процеса на оторизация",
"INVALID_TOKEN": "Предоставен е невалиден токен",
"TOKEN_NOT_FOUND": "Токенът не е намерен"
},
"SUCCESS": {
"SETTINGS_SAVED": "Настройките на Zapier са запазени успешно",
"DELETE_WEBHOOK": "Уебхукът е изтрит успешно"
"DELETE_WEBHOOK": "Уебхукът е изтрит успешно",
"CREATE_ZAP": "Zap-ът е създаден успешно"
},
"TAB": {
"TRIGGERS": "Тригери",
@@ -1409,6 +1492,26 @@
"WEBHOOKS": {
"TITLE": "Уебхукове",
"NO_WEBHOOKS": "Няма конфигурирани уебхукове"
},
"ZAPS": {
"TITLE": "Запс",
"NO_ZAPS": "Няма налични запс",
"CREATE": "Създай Запс",
"SUBMIT": "Създай",
"ADD_STEP": "Добави стъпка",
"OPEN_EDITOR": "Отвори в Zapier",
"ENABLED": "Активирано",
"DISABLED": "Деактивирано",
"TITLE_PLACEHOLDER": "Въведете заглавие за новия Запс",
"FIELDS": {
"TITLE": "Заглавие",
"STEPS": "Стъпки",
"STEP": "Стъпка",
"STEP_TITLE": "Заглавие на стъпката",
"ACTION": "Действие",
"AUTHENTICATION": "Удостоверяване",
"INPUTS": "Входни данни (JSON)"
}
}
},
"GAUZY_AI_PAGE": {
@@ -1687,7 +1790,17 @@
"INTEGRATION_ADDED": "Интеграцията с '{{provider}}' беше добавена към '{{organization}}'"
},
"ENABLED": "Активирано",
"DISABLED": "Деактивирано"
"DISABLED": "Деактивирано",
"MAKE_PAGE": {
"PROCESSING_AUTHORIZATION": "Обработка на оторизацията на Make.com...",
"ERRORS": {
"AUTHORIZATION_FAILED": "Оторизацията на Make.com е неуспешна",
"INVALID_CALLBACK": "Невалидни параметри за обратно извикване"
},
"SUCCESS": {
"AUTHORIZATION_COMPLETE": "Оторизацията на Make.com е завършена успешно"
}
}
},
"DASHBOARD_PAGE": {
"ACCOUNTING": "Счетоводство",
+126 -13
View File
@@ -1294,16 +1294,93 @@
},
"MAKE_COM_PAGE": {
"SCENARIOS": "Szenarien",
"EXECUTIONS": "Ausführungen",
"HISTORY": "Verlauf",
"WEBHOOKS": "Webhooks",
"WEBHOOKS_REFRESH": "Webhooks aktualisieren",
"WEBHOOK_ENABLE": "Webhook aktivieren",
"WEBHOOK_DISABLE": "Webhook deaktivieren",
"WEBHOOK_PING": "Webhook testen",
"SCENARIO_ACTIVE": "Aktiv",
"SCENARIO_INACTIVE": "Inaktiv",
"SCENARIO_LAST_EDITED": "Zuletzt bearbeitet: {{value}}",
"SCENARIO_DISABLE": "Szenario deaktivieren",
"SCENARIO_ENABLE": "Szenario aktivieren",
"SCENARIO_RUN_ONCE": "Szenario einmal ausführen",
"WEBHOOK_ENABLED": "Aktiviert",
"WEBHOOK_DISABLED": "Deaktiviert",
"WEBHOOK_TYPE": "Typ",
"WEBHOOK_QUEUE": "Warteschlange",
"TEMPLATE_PUBLIC": "Öffentlich",
"TEMPLATE_PUBLISHED": "Veröffentlicht",
"REFRESH_CONNECTIONS": "Verbindungen aktualisieren",
"CONFIGURATION_TITLE": "Make.com-Konfiguration",
"ZONE_LABEL": "Zone",
"ZONE_HINT": "Wählen Sie die Zone, die mit Ihrem Make.com-Konto übereinstimmt (z.B. us2.make.com)",
"ORGANIZATION_LABEL": "Make.com-Organisation",
"LOADING_ORGANIZATIONS": "Organisationen werden geladen...",
"NO_ORGANIZATIONS": "Keine Organisationen gefunden",
"TEAM_LABEL": "Make.com-Team",
"LOADING_TEAMS": "Teams werden geladen...",
"NO_TEAMS": "Keine Teams gefunden",
"CONNECTED_TO": "Verbunden mit {{org}} / {{team}} auf {{zone}}",
"WEBHOOK_SETTINGS_TITLE": "Webhook-Einstellungen",
"CONNECTIONS": "Verbindungen",
"TEMPLATES": "Vorlagen",
"SETTINGS": "Einstellungen",
"ERRORS": {
"INVALID_FORM": "Bitte füllen Sie alle erforderlichen Felder aus",
"START_AUTHORIZATION": "Bitte starten Sie den Autorisierungsprozess"
"CONNECT_TO_MAKE_COM": "Mit Make.com verbinden",
"CONNECT_INFO": "Klicken Sie auf die Schaltfläche unten, um Ihr Make.com-Konto zu verbinden. Sie werden zu Make.com weitergeleitet, um die Integration zu autorisieren.",
"NO_SCENARIOS": "Keine Szenarien gefunden. Erstellen Sie Szenarien in Ihrem Make.com-Dashboard.",
"NO_HOOKS": "Keine Webhooks gefunden. Erstellen Sie Webhooks in Ihrem Make.com-Dashboard.",
"NO_CONNECTIONS": "Keine Verbindungen gefunden. Erstellen Sie Verbindungen in Ihrem Make.com-Dashboard.",
"NO_TEMPLATES": "Keine Vorlagen gefunden.",
"TEST_CONNECTION": "Verbindung testen",
"CONNECTION": {
"ACCOUNT": "Konto: {{value}}",
"TYPE": "Typ: {{value}}",
"PACKAGE": "Paket: {{value}}",
"EXPIRES": "Läuft ab: {{value}}"
},
"CLIENT_ID": "Client-ID",
"CLIENT_SECRET": "Client-Geheimnis",
"NEXT_STEP_INFO": "Nach der Autorisierung werden Sie zu Make.com weitergeleitet, um die Integration abzuschließen"
"ERRORS": {
"INVALID_FORM": "Bitte füllen Sie alle Pflichtfelder aus",
"START_AUTHORIZATION": "Fehler beim Starten des Autorisierungsprozesses",
"NO_ORGANIZATION": "Bitte wählen Sie zuerst eine Organisation aus",
"LOAD_SETTINGS": "Fehler beim Laden der Make.com-Einstellungen",
"SAVE_SETTINGS": "Fehler beim Speichern der Make.com-Einstellungen",
"LOAD_SETUP_STATUS": "Fehler beim Laden des Einrichtungsstatus",
"LOAD_SCENARIOS": "Fehler beim Laden der Szenarien",
"TOGGLE_SCENARIO": "Fehler beim Umschalten des Szenarios",
"RUN_SCENARIO": "Fehler beim Ausführen des Szenarios",
"LOAD_CONNECTIONS": "Fehler beim Laden der Verbindungen",
"TEST_CONNECTION": "Fehler beim Testen der Verbindung",
"LOAD_WEBHOOKS": "Fehler beim Laden der Webhooks",
"TOGGLE_WEBHOOK": "Fehler beim Umschalten des Webhooks",
"PING_WEBHOOK": "Fehler beim Testen des Webhooks",
"SET_ZONE": "Fehler beim Festlegen der Zone",
"LOAD_ORGANIZATIONS": "Fehler beim Laden der Organisationen",
"SELECT_ORGANIZATION": "Fehler beim Auswählen der Organisation",
"LOAD_TEAMS": "Fehler beim Laden der Teams",
"SELECT_TEAM": "Fehler beim Auswählen des Teams",
"LOAD_TEMPLATES": "Fehler beim Laden der Vorlagen",
"REQUEST_TIMEOUT": "Zeitüberschreitung der Anfrage. Bitte versuchen Sie es erneut."
},
"SUCCESS": {
"SETTINGS_SAVED": "Make.com-Einstellungen erfolgreich gespeichert",
"SCENARIO_ACTIVATED": "Szenario aktiviert",
"SCENARIO_DEACTIVATED": "Szenario deaktiviert",
"SCENARIO_EXECUTED": "Szenario ausgeführt",
"CONNECTION_VALID": "Verbindung \"{{name}}\" ist gültig",
"WEBHOOK_TOGGLED_ON": "Webhook aktiviert",
"WEBHOOK_TOGGLED_OFF": "Webhook deaktiviert",
"WEBHOOK_PINGED": "Webhook \"{{name}}\" erfolgreich getestet",
"ZONE_SET": "Zone auf {{zone}} gesetzt",
"ORGANIZATION_SELECTED": "Organisation \"{{name}}\" ausgewählt",
"TEAM_SELECTED": "Team \"{{name}}\" ausgewählt"
},
"SETUP_REQUIRED": {
"AUTHORIZE": "Bitte autorisieren Sie zuerst Make.com. Gehen Sie zur Registerkarte Einstellungen, um Ihr Konto zu verbinden.",
"ZONE": "Bitte wählen Sie Ihre Make.com-Zone in der Registerkarte Einstellungen.",
"ORGANIZATION": "Bitte wählen Sie Ihre Make.com-Organisation in der Registerkarte Einstellungen.",
"TEAM": "Bitte wählen Sie Ihr Make.com-Team in der Registerkarte Einstellungen."
}
},
"MAKE_COM": {
"SETTINGS": {
@@ -1316,26 +1393,32 @@
}
},
"ZAPIER_PAGE": {
"CLIENT_ID": "Client ID",
"CLIENT_SECRET": "Client Secret",
"NEXT_STEP_INFO": "Nach der Autorisierung werden Sie zu Zapier weitergeleitet, um die Integration abzuschließen",
"CONNECT_TO_ZAPIER": "Mit Zapier verbinden",
"CONNECT_INFO": "Klicken Sie auf die Schaltfläche unten, um Ihr Zapier-Konto zu verbinden. Sie werden zu Zapier weitergeleitet, um die Integration zu autorisieren.",
"ERRORS": {
"LOAD_SETTINGS": "Fehler beim Laden der Zapier-Einstellungen",
"SAVE_SETTINGS": "Fehler beim Speichern der Zapier-Einstellungen",
"INVALID_FORM": "Bitte füllen Sie alle erforderlichen Felder aus",
"MISSING_CREDENTIALS": "Fehlende Client-Anmeldedaten",
"NO_ORGANIZATION": "Bitte wählen Sie zuerst eine Organisation aus",
"NO_ACCESS_TOKEN": "Kein Zugriffstoken verfügbar",
"LOAD_WEBHOOKS": "Fehler beim Laden der Webhooks",
"DELETE_WEBHOOK": "Fehler beim Löschen des Webhooks",
"LOAD_ACTIONS": "Fehler beim Laden der Aktionen",
"LOAD_TRIGGERS": "Fehler beim Laden der Trigger",
"LOAD_ZAPS": "Fehler beim Laden der Zaps",
"LOAD_ZAP_TEMPLATES": "Fehler beim Laden der Zap-Vorlagen",
"CREATE_ZAP": "Fehler beim Erstellen des Zaps",
"ZAP_TITLE_REQUIRED": "Bitte geben Sie einen Titel für den neuen Zap an",
"NO_INTEGRATION_ID": "Es wurde keine Zapier-Integrations-ID angegeben",
"START_AUTHORIZATION": "Bitte starten Sie den Autorisierungsprozess",
"INVALID_TOKEN": "Ungültiges Token bereitgestellt",
"TOKEN_NOT_FOUND": "Token nicht gefunden"
},
"SUCCESS": {
"SETTINGS_SAVED": "Zapier-Einstellungen erfolgreich gespeichert",
"DELETE_WEBHOOK": "Webhook erfolgreich gelöscht"
"DELETE_WEBHOOK": "Webhook erfolgreich gelöscht",
"CREATE_ZAP": "Zap erfolgreich erstellt"
},
"TAB": {
"TRIGGERS": "Trigger",
@@ -1377,6 +1460,26 @@
"WEBHOOKS": {
"TITLE": "Webhooks",
"NO_WEBHOOKS": "Keine Webhooks konfiguriert"
},
"ZAPS": {
"TITLE": "Zaps",
"NO_ZAPS": "Keine Zaps verfügbar",
"CREATE": "Zap erstellen",
"SUBMIT": "Erstellen",
"ADD_STEP": "Schritt hinzufügen",
"OPEN_EDITOR": "In Zapier öffnen",
"ENABLED": "Aktiviert",
"DISABLED": "Deaktiviert",
"TITLE_PLACEHOLDER": "Geben Sie einen Titel für den neuen Zap ein",
"FIELDS": {
"TITLE": "Titel",
"STEPS": "Schritte",
"STEP": "Schritt",
"STEP_TITLE": "Schritttitel",
"ACTION": "Aktion",
"AUTHENTICATION": "Authentifizierung",
"INPUTS": "Eingaben (JSON)"
}
}
},
"GAUZY_AI_PAGE": {
@@ -1609,7 +1712,17 @@
"FILTER_INTEGRATIONS": "Filterintegrationen",
"SEARCH_INTEGRATIONS": "Suchen Sie nach Integrationen.",
"PAID": "bezahlt",
"INTEGRATION": "Integration"
"INTEGRATION": "Integration",
"MAKE_PAGE": {
"PROCESSING_AUTHORIZATION": "Make.com-Autorisierung wird verarbeitet...",
"ERRORS": {
"AUTHORIZATION_FAILED": "Make.com-Autorisierung fehlgeschlagen",
"INVALID_CALLBACK": "Ungültige Callback-Parameter"
},
"SUCCESS": {
"AUTHORIZATION_COMPLETE": "Make.com-Autorisierung erfolgreich abgeschlossen"
}
}
},
"DASHBOARD_PAGE": {
"ACCOUNTING": "Buchhaltung",
+132 -11
View File
@@ -1343,16 +1343,93 @@
},
"MAKE_COM_PAGE": {
"SCENARIOS": "Scenarios",
"EXECUTIONS": "Executions",
"HISTORY": "History",
"WEBHOOKS": "Webhooks",
"WEBHOOKS_REFRESH": "Refresh webhooks",
"WEBHOOK_ENABLE": "Enable webhook",
"WEBHOOK_DISABLE": "Disable webhook",
"WEBHOOK_PING": "Ping webhook",
"SCENARIO_ACTIVE": "Active",
"SCENARIO_INACTIVE": "Inactive",
"SCENARIO_LAST_EDITED": "Last edited: {{value}}",
"SCENARIO_DISABLE": "Disable scenario",
"SCENARIO_ENABLE": "Enable scenario",
"SCENARIO_RUN_ONCE": "Run scenario once",
"WEBHOOK_ENABLED": "Enabled",
"WEBHOOK_DISABLED": "Disabled",
"WEBHOOK_TYPE": "Type",
"WEBHOOK_QUEUE": "Queue",
"TEMPLATE_PUBLIC": "Public",
"TEMPLATE_PUBLISHED": "Published",
"REFRESH_CONNECTIONS": "Refresh connections",
"CONFIGURATION_TITLE": "Make.com Configuration",
"ZONE_LABEL": "Zone",
"ZONE_HINT": "Select the zone that matches your Make.com account (e.g., us2.make.com)",
"ORGANIZATION_LABEL": "Make.com Organization",
"LOADING_ORGANIZATIONS": "Loading organizations...",
"NO_ORGANIZATIONS": "No organizations found",
"TEAM_LABEL": "Make.com Team",
"LOADING_TEAMS": "Loading teams...",
"NO_TEAMS": "No teams found",
"CONNECTED_TO": "Connected to {{org}} / {{team}} on {{zone}}",
"WEBHOOK_SETTINGS_TITLE": "Webhook Settings",
"CONNECTIONS": "Connections",
"TEMPLATES": "Templates",
"SETTINGS": "Settings",
"CONNECT_TO_MAKE_COM": "Connect to Make.com",
"CONNECT_INFO": "Click the button below to connect your Make.com account. You will be redirected to Make.com to authorize the integration.",
"NO_SCENARIOS": "No scenarios found. Create scenarios in your Make.com dashboard.",
"NO_HOOKS": "No webhooks found. Create webhooks in your Make.com dashboard.",
"NO_CONNECTIONS": "No connections found. Create connections in your Make.com dashboard.",
"NO_TEMPLATES": "No templates found.",
"TEST_CONNECTION": "Test connection",
"CONNECTION": {
"ACCOUNT": "Account: {{value}}",
"TYPE": "Type: {{value}}",
"PACKAGE": "Package: {{value}}",
"EXPIRES": "Expires: {{value}}"
},
"ERRORS": {
"INVALID_FORM": "Please fill in all required fields",
"START_AUTHORIZATION": "Please start the authorization process"
"START_AUTHORIZATION": "Failed to start the authorization process",
"NO_ORGANIZATION": "Please select an organization first",
"LOAD_SETTINGS": "Failed to load Make.com settings",
"SAVE_SETTINGS": "Failed to save Make.com settings",
"LOAD_SETUP_STATUS": "Failed to load setup status",
"LOAD_SCENARIOS": "Failed to load scenarios",
"TOGGLE_SCENARIO": "Failed to toggle scenario",
"RUN_SCENARIO": "Failed to run scenario",
"LOAD_CONNECTIONS": "Failed to load connections",
"TEST_CONNECTION": "Failed to test connection",
"LOAD_WEBHOOKS": "Failed to load webhooks",
"TOGGLE_WEBHOOK": "Failed to toggle webhook",
"PING_WEBHOOK": "Failed to ping webhook",
"SET_ZONE": "Failed to set zone",
"LOAD_ORGANIZATIONS": "Failed to load organizations",
"SELECT_ORGANIZATION": "Failed to select organization",
"LOAD_TEAMS": "Failed to load teams",
"SELECT_TEAM": "Failed to select team",
"LOAD_TEMPLATES": "Failed to load templates",
"REQUEST_TIMEOUT": "Request timed out. Please try again."
},
"CLIENT_ID": "Client ID",
"CLIENT_SECRET": "Client Secret",
"NEXT_STEP_INFO": "After authorization, you will be redirected to Make.com to complete the integration setup"
"SUCCESS": {
"SETTINGS_SAVED": "Make.com settings saved successfully",
"SCENARIO_ACTIVATED": "Scenario activated",
"SCENARIO_DEACTIVATED": "Scenario deactivated",
"SCENARIO_EXECUTED": "Scenario executed",
"CONNECTION_VALID": "Connection \"{{name}}\" is valid",
"WEBHOOK_TOGGLED_ON": "Webhook enabled",
"WEBHOOK_TOGGLED_OFF": "Webhook disabled",
"WEBHOOK_PINGED": "Webhook \"{{name}}\" pinged successfully",
"ZONE_SET": "Zone set to {{zone}}",
"ORGANIZATION_SELECTED": "Organization \"{{name}}\" selected",
"TEAM_SELECTED": "Team \"{{name}}\" selected"
},
"SETUP_REQUIRED": {
"AUTHORIZE": "Please authorize with Make.com first. Go to the Settings tab to connect your account.",
"ZONE": "Please select your Make.com zone in the Settings tab.",
"ORGANIZATION": "Please select your Make.com organization in the Settings tab.",
"TEAM": "Please select your Make.com team in the Settings tab."
}
},
"MAKE_COM": {
"SETTINGS": {
@@ -1365,31 +1442,40 @@
}
},
"ZAPIER_PAGE": {
"CLIENT_ID": "Client ID",
"CLIENT_SECRET": "Client Secret",
"NEXT_STEP_INFO": "After authorization, you will be redirected to Zapier to complete the integration setup",
"CONNECT_TO_ZAPIER": "Connect to Zapier",
"CONNECT_INFO": "Click the button below to connect your Zapier account. You will be redirected to Zapier to authorize the integration.",
"ERRORS": {
"LOAD_SETTINGS": "Failed to load Zapier settings",
"SAVE_SETTINGS": "Failed to save Zapier settings",
"INVALID_FORM": "Please fill in all required fields",
"MISSING_CREDENTIALS": "Missing client credentials",
"NO_ORGANIZATION": "Please select an organization first",
"NO_ACCESS_TOKEN": "No access token available",
"LOAD_WEBHOOKS": "Failed to load webhooks",
"DELETE_WEBHOOK": "Failed to delete webhook",
"LOAD_ACTIONS": "Failed to load actions",
"LOAD_TRIGGERS": "Failed to load triggers",
"LOAD_ZAPS": "Failed to load zaps",
"LOAD_ZAP_TEMPLATES": "Failed to load zap templates",
"CREATE_ZAP": "Failed to create zap",
"ZAP_TITLE_REQUIRED": "Please provide a title for the new zap",
"INVALID_STEP_INPUTS": "Step {{step}} has invalid JSON in the inputs field",
"NO_INTEGRATION_ID": "No Zapier integration ID was provided",
"START_AUTHORIZATION": "Please start the authorization process",
"INVALID_TOKEN": "Invalid token provided",
"TOKEN_NOT_FOUND": "Token not found"
},
"SUCCESS": {
"SETTINGS_SAVED": "Zapier settings saved successfully",
"DELETE_WEBHOOK": "Webhook deleted successfully"
"DELETE_WEBHOOK": "Webhook deleted successfully",
"CREATE_ZAP": "Zap created successfully"
},
"TAB": {
"TRIGGERS": "Triggers",
"ACTIONS": "Actions",
"WEBHOOKS": "Webhooks",
"ZAPS": "Zaps",
"ZAP_TEMPLATES": "Zap Templates",
"SETTINGS": "Settings"
},
"TOOLTIP": {
@@ -1426,6 +1512,31 @@
"WEBHOOKS": {
"TITLE": "Webhooks",
"NO_WEBHOOKS": "No webhooks configured"
},
"ZAPS": {
"TITLE": "Zaps",
"NO_ZAPS": "No zaps available",
"CREATE": "Create Zap",
"SUBMIT": "Create",
"ADD_STEP": "Add step",
"OPEN_EDITOR": "Open in Zapier",
"ENABLED": "Enabled",
"DISABLED": "Disabled",
"TITLE_PLACEHOLDER": "Enter a title for the new Zap",
"FIELDS": {
"TITLE": "Title",
"STEPS": "Steps",
"STEP": "Step",
"STEP_TITLE": "Step title",
"ACTION": "Action",
"AUTHENTICATION": "Authentication",
"INPUTS": "Inputs (JSON)"
}
},
"ZAP_TEMPLATES": {
"TITLE": "Zap Templates",
"NO_TEMPLATES": "No zap templates available",
"USE": "Use Template"
}
},
"GAUZY_AI_PAGE": {
@@ -1709,7 +1820,17 @@
"INTEGRATION_ADDED": "Integration '{{provider}}' has been added to '{{organization}}'"
},
"ENABLED": "Enabled",
"DISABLED": "Disabled"
"DISABLED": "Disabled",
"MAKE_PAGE": {
"PROCESSING_AUTHORIZATION": "Processing Make.com authorization...",
"ERRORS": {
"AUTHORIZATION_FAILED": "Make.com authorization failed",
"INVALID_CALLBACK": "Invalid callback parameters"
},
"SUCCESS": {
"AUTHORIZATION_COMPLETE": "Make.com authorization completed successfully"
}
}
},
"DASHBOARD_PAGE": {
"ACCOUNTING": "Accounting",
+126 -13
View File
@@ -1296,16 +1296,93 @@
},
"MAKE_COM_PAGE": {
"SCENARIOS": "Escenarios",
"EXECUTIONS": "Ejecuciones",
"HISTORY": "Historial",
"WEBHOOKS": "Webhooks",
"WEBHOOKS_REFRESH": "Actualizar webhooks",
"WEBHOOK_ENABLE": "Activar webhook",
"WEBHOOK_DISABLE": "Desactivar webhook",
"WEBHOOK_PING": "Probar webhook",
"SCENARIO_ACTIVE": "Activo",
"SCENARIO_INACTIVE": "Inactivo",
"SCENARIO_LAST_EDITED": "Última edición: {{value}}",
"SCENARIO_DISABLE": "Desactivar escenario",
"SCENARIO_ENABLE": "Activar escenario",
"SCENARIO_RUN_ONCE": "Ejecutar escenario una vez",
"WEBHOOK_ENABLED": "Activado",
"WEBHOOK_DISABLED": "Desactivado",
"WEBHOOK_TYPE": "Tipo",
"WEBHOOK_QUEUE": "Cola",
"TEMPLATE_PUBLIC": "Público",
"TEMPLATE_PUBLISHED": "Publicado",
"REFRESH_CONNECTIONS": "Actualizar conexiones",
"CONFIGURATION_TITLE": "Configuración de Make.com",
"ZONE_LABEL": "Zona",
"ZONE_HINT": "Seleccione la zona que coincide con su cuenta de Make.com (ej. us2.make.com)",
"ORGANIZATION_LABEL": "Organización Make.com",
"LOADING_ORGANIZATIONS": "Cargando organizaciones...",
"NO_ORGANIZATIONS": "No se encontraron organizaciones",
"TEAM_LABEL": "Equipo Make.com",
"LOADING_TEAMS": "Cargando equipos...",
"NO_TEAMS": "No se encontraron equipos",
"CONNECTED_TO": "Conectado a {{org}} / {{team}} en {{zone}}",
"WEBHOOK_SETTINGS_TITLE": "Configuración de Webhook",
"CONNECTIONS": "Conexiones",
"TEMPLATES": "Plantillas",
"SETTINGS": "Configuración",
"ERRORS": {
"INVALID_FORM": "Por favor, complete todos los campos requeridos",
"START_AUTHORIZATION": "Por favor, inicie el proceso de autorización"
"CONNECT_TO_MAKE_COM": "Conectar a Make.com",
"CONNECT_INFO": "Haga clic en el botón de abajo para conectar su cuenta de Make.com. Será redirigido a Make.com para autorizar la integración.",
"NO_SCENARIOS": "No se encontraron escenarios. Cree escenarios en su panel de Make.com.",
"NO_HOOKS": "No se encontraron webhooks. Cree webhooks en su panel de Make.com.",
"NO_CONNECTIONS": "No se encontraron conexiones. Cree conexiones en su panel de Make.com.",
"NO_TEMPLATES": "No se encontraron plantillas.",
"TEST_CONNECTION": "Probar conexión",
"CONNECTION": {
"ACCOUNT": "Cuenta: {{value}}",
"TYPE": "Tipo: {{value}}",
"PACKAGE": "Paquete: {{value}}",
"EXPIRES": "Caduca: {{value}}"
},
"CLIENT_ID": "ID de Cliente",
"CLIENT_SECRET": "Clave Secreta del Cliente",
"NEXT_STEP_INFO": "Después de la autorización, será redirigido a Make.com para completar la configuración de la integración"
"ERRORS": {
"INVALID_FORM": "Por favor complete todos los campos obligatorios",
"START_AUTHORIZATION": "Error al iniciar el proceso de autorización",
"NO_ORGANIZATION": "Por favor seleccione una organización primero",
"LOAD_SETTINGS": "Error al cargar la configuración de Make.com",
"SAVE_SETTINGS": "Error al guardar la configuración de Make.com",
"LOAD_SETUP_STATUS": "Error al cargar el estado de configuración",
"LOAD_SCENARIOS": "Error al cargar escenarios",
"TOGGLE_SCENARIO": "Error al alternar escenario",
"RUN_SCENARIO": "Error al ejecutar escenario",
"LOAD_CONNECTIONS": "Error al cargar conexiones",
"TEST_CONNECTION": "Error al probar la conexión",
"LOAD_WEBHOOKS": "Error al cargar webhooks",
"TOGGLE_WEBHOOK": "Error al alternar webhook",
"PING_WEBHOOK": "Error al probar webhook",
"SET_ZONE": "Error al establecer la zona",
"LOAD_ORGANIZATIONS": "Error al cargar organizaciones",
"SELECT_ORGANIZATION": "Error al seleccionar organización",
"LOAD_TEAMS": "Error al cargar equipos",
"SELECT_TEAM": "Error al seleccionar equipo",
"LOAD_TEMPLATES": "Error al cargar plantillas",
"REQUEST_TIMEOUT": "La solicitud ha expirado. Por favor, inténtelo de nuevo."
},
"SUCCESS": {
"SETTINGS_SAVED": "Configuración de Make.com guardada con éxito",
"SCENARIO_ACTIVATED": "Escenario activado",
"SCENARIO_DEACTIVATED": "Escenario desactivado",
"SCENARIO_EXECUTED": "Escenario ejecutado",
"CONNECTION_VALID": "La conexión \"{{name}}\" es válida",
"WEBHOOK_TOGGLED_ON": "Webhook activado",
"WEBHOOK_TOGGLED_OFF": "Webhook desactivado",
"WEBHOOK_PINGED": "Webhook \"{{name}}\" probado con éxito",
"ZONE_SET": "Zona establecida en {{zone}}",
"ORGANIZATION_SELECTED": "Organización \"{{name}}\" seleccionada",
"TEAM_SELECTED": "Equipo \"{{name}}\" seleccionado"
},
"SETUP_REQUIRED": {
"AUTHORIZE": "Por favor autorice Make.com primero. Vaya a la pestaña Configuración para conectar su cuenta.",
"ZONE": "Por favor seleccione su zona de Make.com en la pestaña Configuración.",
"ORGANIZATION": "Por favor seleccione su organización de Make.com en la pestaña Configuración.",
"TEAM": "Por favor seleccione su equipo de Make.com en la pestaña Configuración."
}
},
"MAKE_COM": {
"SETTINGS": {
@@ -1318,26 +1395,32 @@
}
},
"ZAPIER_PAGE": {
"CLIENT_ID": "ID de Cliente",
"CLIENT_SECRET": "Clave Secreta del Cliente",
"NEXT_STEP_INFO": "Después de la autorización, será redirigido a Zapier para completar la configuración de la integración",
"CONNECT_TO_ZAPIER": "Conectar con Zapier",
"CONNECT_INFO": "Haga clic en el botón de abajo para conectar su cuenta de Zapier. Será redirigido a Zapier para autorizar la integración.",
"ERRORS": {
"LOAD_SETTINGS": "Error al cargar la configuración de Zapier",
"SAVE_SETTINGS": "Error al guardar la configuración de Zapier",
"INVALID_FORM": "Por favor, complete todos los campos requeridos",
"MISSING_CREDENTIALS": "Faltan credenciales del cliente",
"NO_ORGANIZATION": "Por favor, seleccione una organización primero",
"NO_ACCESS_TOKEN": "No hay token de acceso disponible",
"LOAD_WEBHOOKS": "Error al cargar webhooks",
"DELETE_WEBHOOK": "Error al eliminar webhook",
"LOAD_ACTIONS": "Error al cargar acciones",
"LOAD_TRIGGERS": "Error al cargar disparadores",
"LOAD_ZAPS": "Error al cargar zaps",
"LOAD_ZAP_TEMPLATES": "Error al cargar plantillas de zap",
"CREATE_ZAP": "Error al crear zap",
"ZAP_TITLE_REQUIRED": "Por favor, proporcione un título para el nuevo zap",
"NO_INTEGRATION_ID": "No se proporcionó ningún ID de integración de Zapier",
"START_AUTHORIZATION": "Por favor, inicie el proceso de autorización",
"INVALID_TOKEN": "Token inválido proporcionado",
"TOKEN_NOT_FOUND": "Token no encontrado"
},
"SUCCESS": {
"SETTINGS_SAVED": "Configuración de Zapier guardada correctamente",
"DELETE_WEBHOOK": "Webhook eliminado correctamente"
"DELETE_WEBHOOK": "Webhook eliminado correctamente",
"CREATE_ZAP": "Zap creado exitosamente"
},
"TAB": {
"TRIGGERS": "Disparadores",
@@ -1379,6 +1462,26 @@
"WEBHOOKS": {
"TITLE": "Webhooks",
"NO_WEBHOOKS": "No hay webhooks configurados"
},
"ZAPS": {
"TITLE": "Zaps",
"NO_ZAPS": "No hay zaps disponibles",
"CREATE": "Crear Zap",
"SUBMIT": "Crear",
"ADD_STEP": "Agregar paso",
"OPEN_EDITOR": "Abrir en Zapier",
"ENABLED": "Habilitado",
"DISABLED": "Deshabilitado",
"TITLE_PLACEHOLDER": "Ingrese un título para el nuevo Zap",
"FIELDS": {
"TITLE": "Título",
"STEPS": "Pasos",
"STEP": "Paso",
"STEP_TITLE": "Título del paso",
"ACTION": "Acción",
"AUTHENTICATION": "Autenticación",
"INPUTS": "Entradas (JSON)"
}
}
},
"GAUZY_AI_PAGE": {
@@ -1611,7 +1714,17 @@
"FILTER_INTEGRATIONS": "Integraciones de filtro",
"SEARCH_INTEGRATIONS": "Búsquedas de integraciones",
"PAID": "Pagado",
"INTEGRATION": "Integración"
"INTEGRATION": "Integración",
"MAKE_PAGE": {
"PROCESSING_AUTHORIZATION": "Procesando autorización de Make.com...",
"ERRORS": {
"AUTHORIZATION_FAILED": "La autorización de Make.com falló",
"INVALID_CALLBACK": "Parámetros de devolución de llamada no válidos"
},
"SUCCESS": {
"AUTHORIZATION_COMPLETE": "Autorización de Make.com completada con éxito"
}
}
},
"DASHBOARD_PAGE": {
"ACCOUNTING": "Contabilidad",
+127 -14
View File
@@ -1295,16 +1295,93 @@
},
"MAKE_COM_PAGE": {
"SCENARIOS": "Scénarios",
"EXECUTIONS": "Exécutions",
"HISTORY": "Historique",
"WEBHOOKS": "Webhooks",
"WEBHOOKS_REFRESH": "Actualiser les webhooks",
"WEBHOOK_ENABLE": "Activer le webhook",
"WEBHOOK_DISABLE": "Désactiver le webhook",
"WEBHOOK_PING": "Tester le webhook",
"SCENARIO_ACTIVE": "Actif",
"SCENARIO_INACTIVE": "Inactif",
"SCENARIO_LAST_EDITED": "Dernière modification : {{value}}",
"SCENARIO_DISABLE": "Désactiver le scénario",
"SCENARIO_ENABLE": "Activer le scénario",
"SCENARIO_RUN_ONCE": "Exécuter le scénario une fois",
"WEBHOOK_ENABLED": "Activé",
"WEBHOOK_DISABLED": "Désactivé",
"WEBHOOK_TYPE": "Type",
"WEBHOOK_QUEUE": "File d'attente",
"TEMPLATE_PUBLIC": "Public",
"TEMPLATE_PUBLISHED": "Publié",
"REFRESH_CONNECTIONS": "Actualiser les connexions",
"CONFIGURATION_TITLE": "Configuration Make.com",
"ZONE_LABEL": "Zone",
"ZONE_HINT": "Sélectionnez la zone qui correspond à votre compte Make.com (ex. us2.make.com)",
"ORGANIZATION_LABEL": "Organisation Make.com",
"LOADING_ORGANIZATIONS": "Chargement des organisations...",
"NO_ORGANIZATIONS": "Aucune organisation trouvée",
"TEAM_LABEL": "Équipe Make.com",
"LOADING_TEAMS": "Chargement des équipes...",
"NO_TEAMS": "Aucune équipe trouvée",
"CONNECTED_TO": "Connecté à {{org}} / {{team}} sur {{zone}}",
"WEBHOOK_SETTINGS_TITLE": "Paramètres Webhook",
"CONNECTIONS": "Connexions",
"TEMPLATES": "Modèles",
"SETTINGS": "Paramètres",
"ERRORS": {
"INVALID_FORM": "Veuillez remplir tous les champs requis",
"START_AUTHORIZATION": "Veuillez démarrer le processus d'autorisation"
"CONNECT_TO_MAKE_COM": "Se connecter à Make.com",
"CONNECT_INFO": "Cliquez sur le bouton ci-dessous pour connecter votre compte Make.com. Vous serez redirigé vers Make.com pour autoriser l'intégration.",
"NO_SCENARIOS": "Aucun scénario trouvé. Créez des scénarios dans votre tableau de bord Make.com.",
"NO_HOOKS": "Aucun webhook trouvé. Créez des webhooks dans votre tableau de bord Make.com.",
"NO_CONNECTIONS": "Aucune connexion trouvée. Créez des connexions dans votre tableau de bord Make.com.",
"NO_TEMPLATES": "Aucun modèle trouvé.",
"TEST_CONNECTION": "Tester la connexion",
"CONNECTION": {
"ACCOUNT": "Compte : {{value}}",
"TYPE": "Type : {{value}}",
"PACKAGE": "Package : {{value}}",
"EXPIRES": "Expire : {{value}}"
},
"CLIENT_ID": "Identifiant Client",
"CLIENT_SECRET": "Clé Secrète Client",
"NEXT_STEP_INFO": "Après l'autorisation, vous serez redirigé vers Make.com pour finaliser la configuration de l'intégration"
"ERRORS": {
"INVALID_FORM": "Veuillez remplir tous les champs obligatoires",
"START_AUTHORIZATION": "Échec du démarrage du processus d'autorisation",
"NO_ORGANIZATION": "Veuillez d'abord sélectionner une organisation",
"LOAD_SETTINGS": "Échec du chargement des paramètres Make.com",
"SAVE_SETTINGS": "Échec de la sauvegarde des paramètres Make.com",
"LOAD_SETUP_STATUS": "Échec du chargement du statut de configuration",
"LOAD_SCENARIOS": "Échec du chargement des scénarios",
"TOGGLE_SCENARIO": "Échec de l'activation/désactivation du scénario",
"RUN_SCENARIO": "Échec de l'exécution du scénario",
"LOAD_CONNECTIONS": "Échec du chargement des connexions",
"TEST_CONNECTION": "Échec du test de connexion",
"LOAD_WEBHOOKS": "Échec du chargement des webhooks",
"TOGGLE_WEBHOOK": "Échec de l'activation/désactivation du webhook",
"PING_WEBHOOK": "Échec du test du webhook",
"SET_ZONE": "Échec de la définition de la zone",
"LOAD_ORGANIZATIONS": "Échec du chargement des organisations",
"SELECT_ORGANIZATION": "Échec de la sélection de l'organisation",
"LOAD_TEAMS": "Échec du chargement des équipes",
"SELECT_TEAM": "Échec de la sélection de l'équipe",
"LOAD_TEMPLATES": "Échec du chargement des modèles",
"REQUEST_TIMEOUT": "La demande a expiré. Veuillez réessayer."
},
"SUCCESS": {
"SETTINGS_SAVED": "Paramètres Make.com enregistrés avec succès",
"SCENARIO_ACTIVATED": "Scénario activé",
"SCENARIO_DEACTIVATED": "Scénario désactivé",
"SCENARIO_EXECUTED": "Scénario exécuté",
"CONNECTION_VALID": "La connexion \"{{name}}\" est valide",
"WEBHOOK_TOGGLED_ON": "Webhook activé",
"WEBHOOK_TOGGLED_OFF": "Webhook désactivé",
"WEBHOOK_PINGED": "Webhook \"{{name}}\" testé avec succès",
"ZONE_SET": "Zone définie sur {{zone}}",
"ORGANIZATION_SELECTED": "Organisation \"{{name}}\" sélectionnée",
"TEAM_SELECTED": "Équipe \"{{name}}\" sélectionnée"
},
"SETUP_REQUIRED": {
"AUTHORIZE": "Veuillez d'abord autoriser Make.com. Allez dans l'onglet Paramètres pour connecter votre compte.",
"ZONE": "Veuillez sélectionner votre zone Make.com dans l'onglet Paramètres.",
"ORGANIZATION": "Veuillez sélectionner votre organisation Make.com dans l'onglet Paramètres.",
"TEAM": "Veuillez sélectionner votre équipe Make.com dans l'onglet Paramètres."
}
},
"MAKE_COM": {
"SETTINGS": {
@@ -1317,26 +1394,32 @@
}
},
"ZAPIER_PAGE": {
"CLIENT_ID": "Identifiant Client",
"CLIENT_SECRET": "Clé Secrète Client",
"NEXT_STEP_INFO": "Après l'autorisation, vous serez redirigé vers Zapier pour finaliser la configuration de l'intégration",
"CONNECT_TO_ZAPIER": "Se connecter à Zapier",
"CONNECT_INFO": "Cliquez sur le bouton ci-dessous pour connecter votre compte Zapier. Vous serez redirigé vers Zapier pour autoriser l'intégration.",
"ERRORS": {
"LOAD_SETTINGS": "Échec du chargement des paramètres Zapier",
"SAVE_SETTINGS": "Échec de l'enregistrement des paramètres Zapier",
"INVALID_FORM": "Veuillez remplir tous les champs requis",
"MISSING_CREDENTIALS": "Identifiants client manquants",
"MISSING_CREDENTIALS": "L'intégration Zapier n'est pas configurée sur le serveur",
"NO_ORGANIZATION": "Veuillez d'abord sélectionner une organisation",
"NO_ACCESS_TOKEN": "Aucun jeton d'accès disponible",
"LOAD_WEBHOOKS": "Échec du chargement des webhooks",
"DELETE_WEBHOOK": "Échec de la suppression du webhook",
"LOAD_ACTIONS": "Échec du chargement des actions",
"LOAD_TRIGGERS": "Échec du chargement des déclencheurs",
"LOAD_ZAPS": "Échec du chargement des zaps",
"LOAD_ZAP_TEMPLATES": "Échec du chargement des modèles de zap",
"CREATE_ZAP": "Échec de la création du zap",
"ZAP_TITLE_REQUIRED": "Veuillez fournir un titre pour le nouveau zap",
"NO_INTEGRATION_ID": "Aucun ID d'intégration Zapier n'a été fourni",
"START_AUTHORIZATION": "Veuillez démarrer le processus d'autorisation",
"INVALID_TOKEN": "Jeton invalide fourni",
"TOKEN_NOT_FOUND": "Jeton non trouvé"
},
"SUCCESS": {
"SETTINGS_SAVED": "Paramètres Zapier enregistrés avec succès",
"DELETE_WEBHOOK": "Webhook supprimé avec succès"
"DELETE_WEBHOOK": "Webhook supprimé avec succès",
"CREATE_ZAP": "Zap créé avec succès"
},
"TAB": {
"TRIGGERS": "Déclencheurs",
@@ -1378,6 +1461,26 @@
"WEBHOOKS": {
"TITLE": "Webhooks",
"NO_WEBHOOKS": "Aucun webhook configuré"
},
"ZAPS": {
"TITLE": "Zaps",
"NO_ZAPS": "Aucun zap disponible",
"CREATE": "Créer un Zap",
"SUBMIT": "Créer",
"ADD_STEP": "Ajouter une étape",
"OPEN_EDITOR": "Ouvrir dans Zapier",
"ENABLED": "Activé",
"DISABLED": "Désactivé",
"TITLE_PLACEHOLDER": "Entrez un titre pour le nouveau Zap",
"FIELDS": {
"TITLE": "Titre",
"STEPS": "Étapes",
"STEP": "Étape",
"STEP_TITLE": "Titre de l'étape",
"ACTION": "Action",
"AUTHENTICATION": "Authentification",
"INPUTS": "Entrées (JSON)"
}
}
},
"GAUZY_AI_PAGE": {
@@ -1610,7 +1713,17 @@
"FILTER_INTEGRATIONS": "Intégrations de filtres",
"SEARCH_INTEGRATIONS": "Intégrations de recherche",
"PAID": "Payé",
"INTEGRATION": "Intégration"
"INTEGRATION": "Intégration",
"MAKE_PAGE": {
"PROCESSING_AUTHORIZATION": "Traitement de l'autorisation Make.com...",
"ERRORS": {
"AUTHORIZATION_FAILED": "L'autorisation Make.com a échoué",
"INVALID_CALLBACK": "Paramètres de rappel invalides"
},
"SUCCESS": {
"AUTHORIZATION_COMPLETE": "Autorisation Make.com terminée avec succès"
}
}
},
"DASHBOARD_PAGE": {
"ACCOUNTING": "Comptabilité",
+129 -16
View File
@@ -1297,17 +1297,94 @@
"HOURLY": "Hourly"
},
"MAKE_COM_PAGE": {
"SCENARIOS": "סצנות",
"EXECUTIONS": "מימושים",
"HISTORY": "היסטוריה",
"SCENARIOS": "תרחישים",
"WEBHOOKS": "Webhooks",
"WEBHOOKS_REFRESH": "רענן webhooks",
"WEBHOOK_ENABLE": "הפעל webhook",
"WEBHOOK_DISABLE": "השבת webhook",
"WEBHOOK_PING": "בדוק webhook",
"SCENARIO_ACTIVE": "פעיל",
"SCENARIO_INACTIVE": "לא פעיל",
"SCENARIO_LAST_EDITED": "עריכה אחרונה: {{value}}",
"SCENARIO_DISABLE": "השבת תרחיש",
"SCENARIO_ENABLE": "הפעל תרחיש",
"SCENARIO_RUN_ONCE": "הפעל תרחיש פעם אחת",
"WEBHOOK_ENABLED": "מופעל",
"WEBHOOK_DISABLED": "מושבת",
"WEBHOOK_TYPE": "סוג",
"WEBHOOK_QUEUE": "תור",
"TEMPLATE_PUBLIC": "ציבורי",
"TEMPLATE_PUBLISHED": "פורסם",
"REFRESH_CONNECTIONS": "רענן חיבורים",
"CONFIGURATION_TITLE": "תצורת Make.com",
"ZONE_LABEL": "אזור",
"ZONE_HINT": "בחר את האזור התואם לחשבון Make.com שלך (למשל us2.make.com)",
"ORGANIZATION_LABEL": "ארגון Make.com",
"LOADING_ORGANIZATIONS": "טוען ארגונים...",
"NO_ORGANIZATIONS": "לא נמצאו ארגונים",
"TEAM_LABEL": "צוות Make.com",
"LOADING_TEAMS": "טוען צוותים...",
"NO_TEAMS": "לא נמצאו צוותים",
"CONNECTED_TO": "מחובר ל-{{org}} / {{team}} ב-{{zone}}",
"WEBHOOK_SETTINGS_TITLE": "הגדרות Webhook",
"CONNECTIONS": "חיבורים",
"TEMPLATES": "תבניות",
"SETTINGS": "הגדרות",
"CONNECT_TO_MAKE_COM": "התחבר ל-Make.com",
"CONNECT_INFO": "לחץ על הכפתור למטה כדי לחבר את חשבון ה-Make.com שלך. תועבר ל-Make.com כדי לאשר את האינטגרציה.",
"NO_SCENARIOS": "לא נמצאו תרחישים. צור תרחישים בלוח הבקרה של Make.com.",
"NO_HOOKS": "לא נמצאו webhooks. צור webhooks בלוח הבקרה של Make.com.",
"NO_CONNECTIONS": "לא נמצאו חיבורים. צור חיבורים בלוח הבקרה של Make.com.",
"NO_TEMPLATES": "לא נמצאו תבניות.",
"TEST_CONNECTION": "בדיקת חיבור",
"CONNECTION": {
"ACCOUNT": "חשבון: {{value}}",
"TYPE": "סוג: {{value}}",
"PACKAGE": "חבילה: {{value}}",
"EXPIRES": "פג תוקף: {{value}}"
},
"ERRORS": {
"INVALID_FORM": "אנא מלא את כל השדות הנדרשים",
"START_AUTHORIZATION": "אנא התחיל את תהליך האישור"
"START_AUTHORIZATION": "כשל בהתחלת תהליך ההרשאה",
"NO_ORGANIZATION": "אנא בחר ארגון תחילה",
"LOAD_SETTINGS": "כשל בטעינת הגדרות Make.com",
"SAVE_SETTINGS": "כשל בשמירת הגדרות Make.com",
"LOAD_SETUP_STATUS": "כשל בטעינת סטטוס הגדרה",
"LOAD_SCENARIOS": "כשל בטעינת תרחישים",
"TOGGLE_SCENARIO": "כשל בהחלפת מצב תרחיש",
"RUN_SCENARIO": "כשל בהפעלת תרחיש",
"LOAD_CONNECTIONS": "כשל בטעינת חיבורים",
"TEST_CONNECTION": "כשל בבדיקת חיבור",
"LOAD_WEBHOOKS": "כשל בטעינת webhooks",
"TOGGLE_WEBHOOK": "כשל בהחלפת מצב webhook",
"PING_WEBHOOK": "כשל בבדיקת webhook",
"SET_ZONE": "כשל בהגדרת אזור",
"LOAD_ORGANIZATIONS": "כשל בטעינת ארגונים",
"SELECT_ORGANIZATION": "כשל בבחירת ארגון",
"LOAD_TEAMS": "כשל בטעינת צוותים",
"SELECT_TEAM": "כשל בבחירת צוות",
"LOAD_TEMPLATES": "כשל בטעינת תבניות",
"REQUEST_TIMEOUT": "פסק זמן לבקשה. אנא נסה שוב."
},
"CLIENT_ID": "מזהה לקוח",
"CLIENT_SECRET": "סיסמת לקוח",
"NEXT_STEP_INFO": "לאחר האישור, תועבר ל-Make.com להשלמת הגדרות האינטגרציה"
"SUCCESS": {
"SETTINGS_SAVED": "הגדרות Make.com נשמרו בהצלחה",
"SCENARIO_ACTIVATED": "תרחיש הופעל",
"SCENARIO_DEACTIVATED": "תרחיש הושבת",
"SCENARIO_EXECUTED": "תרחיש בוצע",
"CONNECTION_VALID": "החיבור \"{{name}}\" תקין",
"WEBHOOK_TOGGLED_ON": "Webhook הופעל",
"WEBHOOK_TOGGLED_OFF": "Webhook הושבת",
"WEBHOOK_PINGED": "Webhook \"{{name}}\" נבדק בהצלחה",
"ZONE_SET": "האזור הוגדר ל-{{zone}}",
"ORGANIZATION_SELECTED": "ארגון \"{{name}}\" נבחר",
"TEAM_SELECTED": "צוות \"{{name}}\" נבחר"
},
"SETUP_REQUIRED": {
"AUTHORIZE": "אנא אשר תחילה את Make.com. עבור ללשונית הגדרות כדי לחבר את החשבון שלך.",
"ZONE": "אנא בחר את אזור ה-Make.com שלך בלשונית הגדרות.",
"ORGANIZATION": "אנא בחר את ארגון ה-Make.com שלך בלשונית הגדרות.",
"TEAM": "אנא בחר את צוות ה-Make.com שלך בלשונית הגדרות."
}
},
"MAKE_COM": {
"SETTINGS": {
@@ -1320,26 +1397,32 @@
}
},
"ZAPIER_PAGE": {
"CLIENT_ID": "מזהה לקוח",
"CLIENT_SECRET": "סיסמת לקוח",
"NEXT_STEP_INFO": "לאחר האישור, תועבר ל-Zapier להשלמת הגדרות האינטגרציה",
"CONNECT_TO_ZAPIER": "התחבר ל-Zapier",
"CONNECT_INFO": "לחץ על הכפתור למטה כדי לחבר את חשבון ה-Zapier שלך. תועבר ל-Zapier כדי לאשר את האינטגרציה.",
"ERRORS": {
"LOAD_SETTINGS": "נכשל בטעינת הגדרות Zapier",
"SAVE_SETTINGS": "נכשל בשמירת הגדרות Zapier",
"INVALID_FORM": "אנא מלא את כל השדות הנדרשים",
"MISSING_CREDENTIALS": "חסרים פרטי גישה של הלקוח",
"MISSING_CREDENTIALS": "שילוב Zapier לא מוגדר בשרת",
"NO_ORGANIZATION": "אנא בחר ארגון תחילה",
"NO_ACCESS_TOKEN": "אין אסימון גישה זמין",
"LOAD_WEBHOOKS": "נכשל בטעינת וובחוקים",
"DELETE_WEBHOOK": "נכשל במחיקת וובחוק",
"LOAD_ACTIONS": "נכשל בטעינת פעולות",
"LOAD_TRIGGERS": "נכשל בטעינת טריגרים",
"LOAD_ZAPS": "כשל בטעינת zaps",
"LOAD_ZAP_TEMPLATES": "כשל בטעינת תבניות zap",
"CREATE_ZAP": "כשל ביצירת zap",
"ZAP_TITLE_REQUIRED": "אנא ספק כותרת ל-zap החדש",
"NO_INTEGRATION_ID": "לא סופק מזהה אינטגרציה של Zapier",
"START_AUTHORIZATION": "אנא התחיל את תהליך האישור",
"INVALID_TOKEN": "אסימון לא תקין סופק",
"TOKEN_NOT_FOUND": "אסימון לא נמצא"
},
"SUCCESS": {
"SETTINGS_SAVED": "הגדרות Zapier נשמרו בהצלחה",
"DELETE_WEBHOOK": "וובחוק נמחק בהצלחה"
"DELETE_WEBHOOK": "וובחוק נמחק בהצלחה",
"CREATE_ZAP": "Zap נוצר בהצלחה"
},
"TAB": {
"TRIGGERS": "טריגרים",
@@ -1381,6 +1464,26 @@
"WEBHOOKS": {
"TITLE": "וובחוקים",
"NO_WEBHOOKS": "אין וובחוקים מוגדרים"
},
"ZAPS": {
"TITLE": "זאפים",
"NO_ZAPS": "אין זאפים זמינים",
"CREATE": "צור זאפ",
"SUBMIT": "צור",
"ADD_STEP": "הוסף שלב",
"OPEN_EDITOR": "פתח ב-Zapier",
"ENABLED": "מופעל",
"DISABLED": "מושבת",
"TITLE_PLACEHOLDER": "הזן כותרת לזאפ החדש",
"FIELDS": {
"TITLE": "כותרת",
"STEPS": "שלבים",
"STEP": "שלב",
"STEP_TITLE": "כותרת שלב",
"ACTION": "פעולה",
"AUTHENTICATION": "אימות",
"INPUTS": "קלטים (JSON)"
}
}
},
"GAUZY_AI_PAGE": {
@@ -1652,8 +1755,18 @@
"INTEGRATION_DISABLED": "'{{provider}}' integration successfully disabled",
"INTEGRATION_ADDED": "Integration '{{provider}}' has been added to '{{organization}}'"
},
"ENABLED": "Enabled",
"DISABLED": "Disabled"
"ENABLED": "מופעל",
"DISABLED": "מושבת",
"MAKE_PAGE": {
"PROCESSING_AUTHORIZATION": "מעבד הרשאת Make.com...",
"ERRORS": {
"AUTHORIZATION_FAILED": "הרשאת Make.com נכשלה",
"INVALID_CALLBACK": "פרמטרי callback לא חוקיים"
},
"SUCCESS": {
"AUTHORIZATION_COMPLETE": "הרשאת Make.com הושלמה בהצלחה"
}
}
},
"DASHBOARD_PAGE": {
"ACCOUNTING": "Accounting",
@@ -1798,8 +1911,8 @@
"END_WORK": "סיום עבודה",
"WORK_ENDED": "העבודה הסתיימה",
"DELETED": "Deleted",
"ENABLED": "Enabled",
"DISABLED": "Disabled",
"ENABLED": "מופעל",
"DISABLED": "מושבת",
"RECURRING_EXPENSE": "Employee Recurring Expense",
"RECURRING_EXPENSE_EDIT": "Edit the current & all future expenses. There will be no change to any past expense.",
"RECURRING_EXPENSE_ADD": "This will add an expense recurring every month.",
+126 -13
View File
@@ -1294,16 +1294,93 @@
},
"MAKE_COM_PAGE": {
"SCENARIOS": "Scenari",
"EXECUTIONS": "Esecuzioni",
"HISTORY": "Cronologia",
"WEBHOOKS": "Webhooks",
"WEBHOOKS_REFRESH": "Aggiorna webhook",
"WEBHOOK_ENABLE": "Abilita webhook",
"WEBHOOK_DISABLE": "Disabilita webhook",
"WEBHOOK_PING": "Testa webhook",
"SCENARIO_ACTIVE": "Attivo",
"SCENARIO_INACTIVE": "Inattivo",
"SCENARIO_LAST_EDITED": "Ultima modifica: {{value}}",
"SCENARIO_DISABLE": "Disabilita scenario",
"SCENARIO_ENABLE": "Abilita scenario",
"SCENARIO_RUN_ONCE": "Esegui scenario una volta",
"WEBHOOK_ENABLED": "Abilitato",
"WEBHOOK_DISABLED": "Disabilitato",
"WEBHOOK_TYPE": "Tipo",
"WEBHOOK_QUEUE": "Coda",
"TEMPLATE_PUBLIC": "Pubblico",
"TEMPLATE_PUBLISHED": "Pubblicato",
"REFRESH_CONNECTIONS": "Aggiorna connessioni",
"CONFIGURATION_TITLE": "Configurazione Make.com",
"ZONE_LABEL": "Zona",
"ZONE_HINT": "Seleziona la zona che corrisponde al tuo account Make.com (es. us2.make.com)",
"ORGANIZATION_LABEL": "Organizzazione Make.com",
"LOADING_ORGANIZATIONS": "Caricamento organizzazioni...",
"NO_ORGANIZATIONS": "Nessuna organizzazione trovata",
"TEAM_LABEL": "Team Make.com",
"LOADING_TEAMS": "Caricamento team...",
"NO_TEAMS": "Nessun team trovato",
"CONNECTED_TO": "Connesso a {{org}} / {{team}} su {{zone}}",
"WEBHOOK_SETTINGS_TITLE": "Impostazioni Webhook",
"CONNECTIONS": "Connessioni",
"TEMPLATES": "Modelli",
"SETTINGS": "Impostazioni",
"ERRORS": {
"INVALID_FORM": "Per favore, compila tutti i campi obbligatori",
"START_AUTHORIZATION": "Per favore, inizia il processo di autorizzazione"
"CONNECT_TO_MAKE_COM": "Connetti a Make.com",
"CONNECT_INFO": "Clicca il pulsante qui sotto per collegare il tuo account Make.com. Verrai reindirizzato a Make.com per autorizzare l'integrazione.",
"NO_SCENARIOS": "Nessuno scenario trovato. Crea scenari nella tua dashboard Make.com.",
"NO_HOOKS": "Nessun webhook trovato. Crea webhook nella tua dashboard Make.com.",
"NO_CONNECTIONS": "Nessuna connessione trovata. Crea connessioni nella tua dashboard Make.com.",
"NO_TEMPLATES": "Nessun modello trovato.",
"TEST_CONNECTION": "Testa connessione",
"CONNECTION": {
"ACCOUNT": "Account: {{value}}",
"TYPE": "Tipo: {{value}}",
"PACKAGE": "Pacchetto: {{value}}",
"EXPIRES": "Scadenza: {{value}}"
},
"CLIENT_ID": "ID Cliente",
"CLIENT_SECRET": "Segreto Cliente",
"NEXT_STEP_INFO": "Dopo l'autorizzazione, sarai reindirizzato a Make.com per completare la configurazione dell'integrazione"
"ERRORS": {
"INVALID_FORM": "Per favore compila tutti i campi obbligatori",
"START_AUTHORIZATION": "Impossibile avviare il processo di autorizzazione",
"NO_ORGANIZATION": "Per favore seleziona prima un'organizzazione",
"LOAD_SETTINGS": "Impossibile caricare le impostazioni di Make.com",
"SAVE_SETTINGS": "Impossibile salvare le impostazioni di Make.com",
"LOAD_SETUP_STATUS": "Impossibile caricare lo stato di configurazione",
"LOAD_SCENARIOS": "Impossibile caricare gli scenari",
"TOGGLE_SCENARIO": "Impossibile modificare lo stato dello scenario",
"RUN_SCENARIO": "Impossibile eseguire lo scenario",
"LOAD_CONNECTIONS": "Impossibile caricare le connessioni",
"TEST_CONNECTION": "Impossibile testare la connessione",
"LOAD_WEBHOOKS": "Impossibile caricare i webhook",
"TOGGLE_WEBHOOK": "Impossibile modificare lo stato del webhook",
"PING_WEBHOOK": "Impossibile testare il webhook",
"SET_ZONE": "Impossibile impostare la zona",
"LOAD_ORGANIZATIONS": "Impossibile caricare le organizzazioni",
"SELECT_ORGANIZATION": "Impossibile selezionare l'organizzazione",
"LOAD_TEAMS": "Impossibile caricare i team",
"SELECT_TEAM": "Impossibile selezionare il team",
"LOAD_TEMPLATES": "Impossibile caricare i modelli",
"REQUEST_TIMEOUT": "Richiesta scaduta. Per favore riprova."
},
"SUCCESS": {
"SETTINGS_SAVED": "Impostazioni di Make.com salvate con successo",
"SCENARIO_ACTIVATED": "Scenario attivato",
"SCENARIO_DEACTIVATED": "Scenario disattivato",
"SCENARIO_EXECUTED": "Scenario eseguito",
"CONNECTION_VALID": "La connessione \"{{name}}\" è valida",
"WEBHOOK_TOGGLED_ON": "Webhook abilitato",
"WEBHOOK_TOGGLED_OFF": "Webhook disabilitato",
"WEBHOOK_PINGED": "Webhook \"{{name}}\" testato con successo",
"ZONE_SET": "Zona impostata su {{zone}}",
"ORGANIZATION_SELECTED": "Organizzazione \"{{name}}\" selezionata",
"TEAM_SELECTED": "Team \"{{name}}\" selezionato"
},
"SETUP_REQUIRED": {
"AUTHORIZE": "Per favore autorizza prima Make.com. Vai alla scheda Impostazioni per collegare il tuo account.",
"ZONE": "Per favore seleziona la tua zona Make.com nella scheda Impostazioni.",
"ORGANIZATION": "Per favore seleziona la tua organizzazione Make.com nella scheda Impostazioni.",
"TEAM": "Per favore seleziona il tuo team Make.com nella scheda Impostazioni."
}
},
"MAKE_COM": {
"SETTINGS": {
@@ -1316,26 +1393,32 @@
}
},
"ZAPIER_PAGE": {
"CLIENT_ID": "ID Cliente",
"CLIENT_SECRET": "Segreto Cliente",
"NEXT_STEP_INFO": "Dopo l'autorizzazione, sarai reindirizzato a Zapier per completare la configurazione dell'integrazione",
"CONNECT_TO_ZAPIER": "Connetti a Zapier",
"CONNECT_INFO": "Clicca il pulsante qui sotto per collegare il tuo account Zapier. Sarai reindirizzato a Zapier per autorizzare l'integrazione.",
"ERRORS": {
"LOAD_SETTINGS": "Impossibile caricare le impostazioni di Zapier",
"SAVE_SETTINGS": "Impossibile salvare le impostazioni di Zapier",
"INVALID_FORM": "Per favore, compila tutti i campi obbligatori",
"MISSING_CREDENTIALS": "Credenziali client mancanti",
"NO_ORGANIZATION": "Per favore, seleziona prima un'organizzazione",
"NO_ACCESS_TOKEN": "Nessun token di accesso disponibile",
"LOAD_WEBHOOKS": "Impossibile caricare i webhook",
"DELETE_WEBHOOK": "Impossibile eliminare il webhook",
"LOAD_ACTIONS": "Impossibile caricare le azioni",
"LOAD_TRIGGERS": "Impossibile caricare i trigger",
"LOAD_ZAPS": "Impossibile caricare gli zap",
"LOAD_ZAP_TEMPLATES": "Impossibile caricare i modelli di zap",
"CREATE_ZAP": "Impossibile creare lo zap",
"ZAP_TITLE_REQUIRED": "Si prega di fornire un titolo per il nuovo zap",
"NO_INTEGRATION_ID": "Non è stato fornito alcun ID di integrazione Zapier",
"START_AUTHORIZATION": "Per favore, inizia il processo di autorizzazione",
"INVALID_TOKEN": "Token non valido fornito",
"TOKEN_NOT_FOUND": "Token non trovato"
},
"SUCCESS": {
"SETTINGS_SAVED": "Impostazioni di Zapier salvate con successo",
"DELETE_WEBHOOK": "Webhook eliminato con successo"
"DELETE_WEBHOOK": "Webhook eliminato con successo",
"CREATE_ZAP": "Zap creato con successo"
},
"TAB": {
"TRIGGERS": "Trigger",
@@ -1377,6 +1460,26 @@
"WEBHOOKS": {
"TITLE": "Webhook",
"NO_WEBHOOKS": "Nessun webhook configurato"
},
"ZAPS": {
"TITLE": "Zap",
"NO_ZAPS": "Nessun zap disponibile",
"CREATE": "Crea Zap",
"SUBMIT": "Crea",
"ADD_STEP": "Aggiungi passaggio",
"OPEN_EDITOR": "Apri in Zapier",
"ENABLED": "Abilitato",
"DISABLED": "Disabilitato",
"TITLE_PLACEHOLDER": "Inserisci un titolo per il nuovo Zap",
"FIELDS": {
"TITLE": "Titolo",
"STEPS": "Passaggi",
"STEP": "Passaggio",
"STEP_TITLE": "Titolo passaggio",
"ACTION": "Azione",
"AUTHENTICATION": "Autenticazione",
"INPUTS": "Input (JSON)"
}
}
},
"GAUZY_AI_PAGE": {
@@ -1609,7 +1712,17 @@
"FILTER_INTEGRATIONS": "Filtra le integrazioni",
"SEARCH_INTEGRATIONS": "Integrazioni di ricerca",
"PAID": "Pagato",
"INTEGRATION": "Integrazione"
"INTEGRATION": "Integrazione",
"MAKE_PAGE": {
"PROCESSING_AUTHORIZATION": "Elaborazione autorizzazione Make.com...",
"ERRORS": {
"AUTHORIZATION_FAILED": "Autorizzazione Make.com fallita",
"INVALID_CALLBACK": "Parametri di callback non validi"
},
"SUCCESS": {
"AUTHORIZATION_COMPLETE": "Autorizzazione Make.com completata con successo"
}
}
},
"DASHBOARD_PAGE": {
"ACCOUNTING": "Contabilità",
+126 -13
View File
@@ -1294,16 +1294,93 @@
},
"MAKE_COM_PAGE": {
"SCENARIOS": "Scenario's",
"EXECUTIONS": "Uitvoeringen",
"HISTORY": "Geschiedenis",
"WEBHOOKS": "Webhooks",
"WEBHOOKS_REFRESH": "Webhooks vernieuwen",
"WEBHOOK_ENABLE": "Webhook inschakelen",
"WEBHOOK_DISABLE": "Webhook uitschakelen",
"WEBHOOK_PING": "Webhook testen",
"SCENARIO_ACTIVE": "Actief",
"SCENARIO_INACTIVE": "Inactief",
"SCENARIO_LAST_EDITED": "Laatst bewerkt: {{value}}",
"SCENARIO_DISABLE": "Scenario uitschakelen",
"SCENARIO_ENABLE": "Scenario inschakelen",
"SCENARIO_RUN_ONCE": "Voer scenario eenmaal uit",
"WEBHOOK_ENABLED": "Ingeschakeld",
"WEBHOOK_DISABLED": "Uitgeschakeld",
"WEBHOOK_TYPE": "Type",
"WEBHOOK_QUEUE": "Wachtrij",
"TEMPLATE_PUBLIC": "Openbaar",
"TEMPLATE_PUBLISHED": "Gepubliceerd",
"REFRESH_CONNECTIONS": "Verbindingen vernieuwen",
"CONFIGURATION_TITLE": "Make.com-configuratie",
"ZONE_LABEL": "Zone",
"ZONE_HINT": "Selecteer de zone die overeenkomt met uw Make.com-account (bijv. us2.make.com)",
"ORGANIZATION_LABEL": "Make.com-organisatie",
"LOADING_ORGANIZATIONS": "Organisaties laden...",
"NO_ORGANIZATIONS": "Geen organisaties gevonden",
"TEAM_LABEL": "Make.com-team",
"LOADING_TEAMS": "Teams laden...",
"NO_TEAMS": "Geen teams gevonden",
"CONNECTED_TO": "Verbonden met {{org}} / {{team}} op {{zone}}",
"WEBHOOK_SETTINGS_TITLE": "Webhook-instellingen",
"CONNECTIONS": "Verbindingen",
"TEMPLATES": "Sjablonen",
"SETTINGS": "Instellingen",
"ERRORS": {
"INVALID_FORM": "Vul alstublieft alle verplichte velden in",
"START_AUTHORIZATION": "Start alstublieft de autorisatieproces"
"CONNECT_TO_MAKE_COM": "Verbinden met Make.com",
"CONNECT_INFO": "Klik op de knop hieronder om uw Make.com-account te verbinden. U wordt doorgestuurd naar Make.com om de integratie te autoriseren.",
"NO_SCENARIOS": "Geen scenario's gevonden. Maak scenario's aan in uw Make.com-dashboard.",
"NO_HOOKS": "Geen webhooks gevonden. Maak webhooks aan in uw Make.com-dashboard.",
"NO_CONNECTIONS": "Geen verbindingen gevonden. Maak verbindingen aan in uw Make.com-dashboard.",
"NO_TEMPLATES": "Geen sjablonen gevonden.",
"TEST_CONNECTION": "Verbinding testen",
"CONNECTION": {
"ACCOUNT": "Account: {{value}}",
"TYPE": "Type: {{value}}",
"PACKAGE": "Pakket: {{value}}",
"EXPIRES": "Verloopt: {{value}}"
},
"CLIENT_ID": "Client ID",
"CLIENT_SECRET": "Client Geheim",
"NEXT_STEP_INFO": "Na autorisatie word je doorgestuurd naar Make.com om de integratie-instellingen te voltooien"
"ERRORS": {
"INVALID_FORM": "Vul alle verplichte velden in",
"START_AUTHORIZATION": "Kan het autorisatieproces niet starten",
"NO_ORGANIZATION": "Selecteer eerst een organisatie",
"LOAD_SETTINGS": "Kan Make.com-instellingen niet laden",
"SAVE_SETTINGS": "Kan Make.com-instellingen niet opslaan",
"LOAD_SETUP_STATUS": "Kan installatiestatus niet laden",
"LOAD_SCENARIOS": "Kan scenario's niet laden",
"TOGGLE_SCENARIO": "Kan scenario niet schakelen",
"RUN_SCENARIO": "Kan scenario niet uitvoeren",
"LOAD_CONNECTIONS": "Kan verbindingen niet laden",
"TEST_CONNECTION": "Kan verbinding niet testen",
"LOAD_WEBHOOKS": "Kan webhooks niet laden",
"TOGGLE_WEBHOOK": "Kan webhook niet schakelen",
"PING_WEBHOOK": "Kan webhook niet testen",
"SET_ZONE": "Kan zone niet instellen",
"LOAD_ORGANIZATIONS": "Kan organisaties niet laden",
"SELECT_ORGANIZATION": "Kan organisatie niet selecteren",
"LOAD_TEAMS": "Kan teams niet laden",
"SELECT_TEAM": "Kan team niet selecteren",
"LOAD_TEMPLATES": "Kan sjablonen niet laden",
"REQUEST_TIMEOUT": "Aanvraagtijd verstreken. Probeer het opnieuw."
},
"SUCCESS": {
"SETTINGS_SAVED": "Make.com-instellingen succesvol opgeslagen",
"SCENARIO_ACTIVATED": "Scenario geactiveerd",
"SCENARIO_DEACTIVATED": "Scenario gedeactiveerd",
"SCENARIO_EXECUTED": "Scenario uitgevoerd",
"CONNECTION_VALID": "Verbinding \"{{name}}\" is geldig",
"WEBHOOK_TOGGLED_ON": "Webhook ingeschakeld",
"WEBHOOK_TOGGLED_OFF": "Webhook uitgeschakeld",
"WEBHOOK_PINGED": "Webhook \"{{name}}\" succesvol getest",
"ZONE_SET": "Zone ingesteld op {{zone}}",
"ORGANIZATION_SELECTED": "Organisatie \"{{name}}\" geselecteerd",
"TEAM_SELECTED": "Team \"{{name}}\" geselecteerd"
},
"SETUP_REQUIRED": {
"AUTHORIZE": "Autoriseer eerst Make.com. Ga naar het tabblad Instellingen om uw account te verbinden.",
"ZONE": "Selecteer uw Make.com-zone in het tabblad Instellingen.",
"ORGANIZATION": "Selecteer uw Make.com-organisatie in het tabblad Instellingen.",
"TEAM": "Selecteer uw Make.com-team in het tabblad Instellingen."
}
},
"MAKE_COM": {
"SETTINGS": {
@@ -1316,26 +1393,32 @@
}
},
"ZAPIER_PAGE": {
"CLIENT_ID": "Client ID",
"CLIENT_SECRET": "Client Geheim",
"NEXT_STEP_INFO": "Na autorisatie word je doorgestuurd naar Zapier om de integratie-instellingen te voltooien",
"CONNECT_TO_ZAPIER": "Verbinden met Zapier",
"CONNECT_INFO": "Klik op de knop hieronder om je Zapier-account te verbinden. Je wordt doorgestuurd naar Zapier om de integratie te autoriseren.",
"ERRORS": {
"LOAD_SETTINGS": "Fout bij het laden van Zapier-instellingen",
"SAVE_SETTINGS": "Fout bij het opslaan van Zapier-instellingen",
"INVALID_FORM": "Vul alstublieft alle verplichte velden in",
"MISSING_CREDENTIALS": "Ontbrekende clientreferenties",
"NO_ORGANIZATION": "Selecteer alstublieft eerst een organisatie",
"NO_ACCESS_TOKEN": "Geen toegangstoken beschikbaar",
"LOAD_WEBHOOKS": "Fout bij het laden van webhooks",
"DELETE_WEBHOOK": "Fout bij het verwijderen van webhook",
"LOAD_ACTIONS": "Fout bij het laden van acties",
"LOAD_TRIGGERS": "Fout bij het laden van triggers",
"LOAD_ZAPS": "Fout bij het laden van zaps",
"LOAD_ZAP_TEMPLATES": "Fout bij het laden van zap-sjablonen",
"CREATE_ZAP": "Fout bij het maken van zap",
"ZAP_TITLE_REQUIRED": "Geef alstublieft een titel voor de nieuwe zap",
"NO_INTEGRATION_ID": "Er is geen Zapier-integratie-ID verstrekt",
"START_AUTHORIZATION": "Start alstublieft de autorisatieproces",
"INVALID_TOKEN": "Ongeldig token verstrekt",
"TOKEN_NOT_FOUND": "Token niet gevonden"
},
"SUCCESS": {
"SETTINGS_SAVED": "Zapier-instellingen succesvol opgeslagen",
"DELETE_WEBHOOK": "Webhook succesvol verwijderd"
"DELETE_WEBHOOK": "Webhook succesvol verwijderd",
"CREATE_ZAP": "Zap succesvol aangemaakt"
},
"TAB": {
"TRIGGERS": "Triggers",
@@ -1377,6 +1460,26 @@
"WEBHOOKS": {
"TITLE": "Webhooks",
"NO_WEBHOOKS": "Geen webhooks geconfigureerd"
},
"ZAPS": {
"TITLE": "Zaps",
"NO_ZAPS": "Geen zaps beschikbaar",
"CREATE": "Zap maken",
"SUBMIT": "Maken",
"ADD_STEP": "Stap toevoegen",
"OPEN_EDITOR": "Openen in Zapier",
"ENABLED": "Ingeschakeld",
"DISABLED": "Uitgeschakeld",
"TITLE_PLACEHOLDER": "Voer een titel in voor de nieuwe Zap",
"FIELDS": {
"TITLE": "Titel",
"STEPS": "Stappen",
"STEP": "Stap",
"STEP_TITLE": "Stap titel",
"ACTION": "Actie",
"AUTHENTICATION": "Authenticatie",
"INPUTS": "Invoer (JSON)"
}
}
},
"GAUZY_AI_PAGE": {
@@ -1609,7 +1712,17 @@
"FILTER_INTEGRATIONS": "Filter integraties",
"SEARCH_INTEGRATIONS": "Zoekintegraties",
"PAID": "Betaald",
"INTEGRATION": "Integratie"
"INTEGRATION": "Integratie",
"MAKE_PAGE": {
"PROCESSING_AUTHORIZATION": "Make.com-autorisatie wordt verwerkt...",
"ERRORS": {
"AUTHORIZATION_FAILED": "Make.com-autorisatie mislukt",
"INVALID_CALLBACK": "Ongeldige callback-parameters"
},
"SUCCESS": {
"AUTHORIZATION_COMPLETE": "Make.com-autorisatie succesvol voltooid"
}
}
},
"DASHBOARD_PAGE": {
"ACCOUNTING": "Boekhouding",
+124 -11
View File
@@ -1293,16 +1293,93 @@
},
"MAKE_COM_PAGE": {
"SCENARIOS": "Scenariusze",
"EXECUTIONS": "Wykonania",
"HISTORY": "Historia",
"WEBHOOKS": "Webhooki",
"WEBHOOKS_REFRESH": "Odśwież webhooki",
"WEBHOOK_ENABLE": "Włącz webhook",
"WEBHOOK_DISABLE": "Wyłącz webhook",
"WEBHOOK_PING": "Testuj webhook",
"SCENARIO_ACTIVE": "Aktywny",
"SCENARIO_INACTIVE": "Nieaktywny",
"SCENARIO_LAST_EDITED": "Ostatnia edycja: {{value}}",
"SCENARIO_DISABLE": "Wyłącz scenariusz",
"SCENARIO_ENABLE": "Włącz scenariusz",
"SCENARIO_RUN_ONCE": "Uruchom scenariusz raz",
"WEBHOOK_ENABLED": "Włączony",
"WEBHOOK_DISABLED": "Wyłączony",
"WEBHOOK_TYPE": "Typ",
"WEBHOOK_QUEUE": "Kolejka",
"TEMPLATE_PUBLIC": "Publiczny",
"TEMPLATE_PUBLISHED": "Opublikowany",
"REFRESH_CONNECTIONS": "Odśwież połączenia",
"CONFIGURATION_TITLE": "Konfiguracja Make.com",
"ZONE_LABEL": "Strefa",
"ZONE_HINT": "Wybierz strefę odpowiadającą Twojemu kontu Make.com (np. us2.make.com)",
"ORGANIZATION_LABEL": "Organizacja Make.com",
"LOADING_ORGANIZATIONS": "Ładowanie organizacji...",
"NO_ORGANIZATIONS": "Nie znaleziono organizacji",
"TEAM_LABEL": "Zespół Make.com",
"LOADING_TEAMS": "Ładowanie zespołów...",
"NO_TEAMS": "Nie znaleziono zespołów",
"CONNECTED_TO": "Połączony z {{org}} / {{team}} na {{zone}}",
"WEBHOOK_SETTINGS_TITLE": "Ustawienia Webhook",
"CONNECTIONS": "Połączenia",
"TEMPLATES": "Szablony",
"SETTINGS": "Ustawienia",
"CONNECT_TO_MAKE_COM": "Połącz z Make.com",
"CONNECT_INFO": "Kliknij poniższy przycisk, aby połączyć swoje konto Make.com. Zostaniesz przekierowany do Make.com w celu autoryzacji integracji.",
"NO_SCENARIOS": "Nie znaleziono scenariuszy. Utwórz scenariusze w panelu Make.com.",
"NO_HOOKS": "Nie znaleziono webhooków. Utwórz webhooki w panelu Make.com.",
"NO_CONNECTIONS": "Nie znaleziono połączeń. Utwórz połączenia w panelu Make.com.",
"NO_TEMPLATES": "Nie znaleziono szablonów.",
"TEST_CONNECTION": "Testuj połączenie",
"CONNECTION": {
"ACCOUNT": "Konto: {{value}}",
"TYPE": "Typ: {{value}}",
"PACKAGE": "Pakiet: {{value}}",
"EXPIRES": "Wygasa: {{value}}"
},
"ERRORS": {
"INVALID_FORM": "Proszę wypełnić wszystkie wymagane pola",
"START_AUTHORIZATION": "Proszę rozpocząć proces autoryzacji"
"START_AUTHORIZATION": "Nie udało się rozpocząć procesu autoryzacji",
"NO_ORGANIZATION": "Proszę najpierw wybrać organizację",
"LOAD_SETTINGS": "Nie udało się załadować ustawień Make.com",
"SAVE_SETTINGS": "Nie udało się zapisać ustawień Make.com",
"LOAD_SETUP_STATUS": "Nie udało się załadować statusu konfiguracji",
"LOAD_SCENARIOS": "Nie udało się załadować scenariuszy",
"TOGGLE_SCENARIO": "Nie udało się przełączyć scenariusza",
"RUN_SCENARIO": "Nie udało się uruchomić scenariusza",
"LOAD_CONNECTIONS": "Nie udało się załadować połączeń",
"TEST_CONNECTION": "Nie udało się przetestować połączenia",
"LOAD_WEBHOOKS": "Nie udało się załadować webhooków",
"TOGGLE_WEBHOOK": "Nie udało się przełączyć webhooka",
"PING_WEBHOOK": "Nie udało się przetestować webhooka",
"SET_ZONE": "Nie udało się ustawić strefy",
"LOAD_ORGANIZATIONS": "Nie udało się załadować organizacji",
"SELECT_ORGANIZATION": "Nie udało się wybrać organizacji",
"LOAD_TEAMS": "Nie udało się załadować zespołów",
"SELECT_TEAM": "Nie udało się wybrać zespołu",
"LOAD_TEMPLATES": "Nie udało się załadować szablonów",
"REQUEST_TIMEOUT": "Upłynął czas żądania. Spróbuj ponownie."
},
"CLIENT_ID": "ID Klienta",
"CLIENT_SECRET": "Sekret Klienta",
"NEXT_STEP_INFO": "Po autoryzacji zostaniesz przekierowany do Make.com, aby dokończyć konfigurację integracji"
"SUCCESS": {
"SETTINGS_SAVED": "Ustawienia Make.com zostały pomyślnie zapisane",
"SCENARIO_ACTIVATED": "Scenariusz aktywowany",
"SCENARIO_DEACTIVATED": "Scenariusz dezaktywowany",
"SCENARIO_EXECUTED": "Scenariusz wykonany",
"CONNECTION_VALID": "Połączenie \"{{name}}\" jest prawidłowe",
"WEBHOOK_TOGGLED_ON": "Webhook włączony",
"WEBHOOK_TOGGLED_OFF": "Webhook wyłączony",
"WEBHOOK_PINGED": "Webhook \"{{name}}\" przetestowany pomyślnie",
"ZONE_SET": "Strefa ustawiona na {{zone}}",
"ORGANIZATION_SELECTED": "Wybrano organizację \"{{name}}\"",
"TEAM_SELECTED": "Wybrano zespół \"{{name}}\""
},
"SETUP_REQUIRED": {
"AUTHORIZE": "Najpierw autoryzuj Make.com. Przejdź do zakładki Ustawienia, aby połączyć konto.",
"ZONE": "Wybierz strefę Make.com w zakładce Ustawienia.",
"ORGANIZATION": "Wybierz organizację Make.com w zakładce Ustawienia.",
"TEAM": "Wybierz zespół Make.com w zakładce Ustawienia."
}
},
"MAKE_COM": {
"SETTINGS": {
@@ -1315,26 +1392,32 @@
}
},
"ZAPIER_PAGE": {
"CLIENT_ID": "ID Klienta",
"CLIENT_SECRET": "Sekret Klienta",
"NEXT_STEP_INFO": "Po autoryzacji zostaniesz przekierowany do Zapier, aby dokończyć konfigurację integracji",
"CONNECT_TO_ZAPIER": "Połącz z Zapier",
"CONNECT_INFO": "Kliknij przycisk poniżej, aby połączyć swoje konto Zapier. Zostaniesz przekierowany do Zapier w celu autoryzacji integracji.",
"ERRORS": {
"LOAD_SETTINGS": "Nie udało się załadować ustawień Zapier",
"SAVE_SETTINGS": "Nie udało się zapisać ustawień Zapier",
"INVALID_FORM": "Proszę wypełnić wszystkie wymagane pola",
"MISSING_CREDENTIALS": "Brakujące dane uwierzytelniające klienta",
"NO_ORGANIZATION": "Proszę najpierw wybrać organizację",
"NO_ACCESS_TOKEN": "Brak dostępnego tokenu dostępu",
"LOAD_WEBHOOKS": "Nie udało się załadować webhooków",
"DELETE_WEBHOOK": "Nie udało się usunąć webhooka",
"LOAD_ACTIONS": "Nie udało się załadować akcji",
"LOAD_TRIGGERS": "Nie udało się załadować triggerów",
"LOAD_ZAPS": "Nie udało się załadować zapów",
"LOAD_ZAP_TEMPLATES": "Nie udało się załadować szablonów zap",
"CREATE_ZAP": "Nie udało się utworzyć zapa",
"ZAP_TITLE_REQUIRED": "Proszę podać tytuł dla nowego zapa",
"NO_INTEGRATION_ID": "Nie podano identyfikatora integracji Zapier",
"START_AUTHORIZATION": "Proszę rozpocząć proces autoryzacji",
"INVALID_TOKEN": "Podano nieprawidłowy token",
"TOKEN_NOT_FOUND": "Token nie został znaleziony"
},
"SUCCESS": {
"SETTINGS_SAVED": "Ustawienia Zapier zostały pomyślnie zapisane",
"DELETE_WEBHOOK": "Webhook został pomyślnie usunięty"
"DELETE_WEBHOOK": "Webhook został pomyślnie usunięty",
"CREATE_ZAP": "Zap został pomyślnie utworzony"
},
"TAB": {
"TRIGGERS": "Wyzwalacze",
@@ -1376,6 +1459,26 @@
"WEBHOOKS": {
"TITLE": "Webhooki",
"NO_WEBHOOKS": "Brak skonfigurowanych webhooków"
},
"ZAPS": {
"TITLE": "Zapy",
"NO_ZAPS": "Brak dostępnych zapów",
"CREATE": "Utwórz Zap",
"SUBMIT": "Utwórz",
"ADD_STEP": "Dodaj krok",
"OPEN_EDITOR": "Otwórz w Zapier",
"ENABLED": "Włączone",
"DISABLED": "Wyłączone",
"TITLE_PLACEHOLDER": "Wprowadź tytuł dla nowego Zapa",
"FIELDS": {
"TITLE": "Tytuł",
"STEPS": "Kroki",
"STEP": "Krok",
"STEP_TITLE": "Tytuł kroku",
"ACTION": "Akcja",
"AUTHENTICATION": "Uwierzytelnianie",
"INPUTS": "Wejścia (JSON)"
}
}
},
"GAUZY_AI_PAGE": {
@@ -1608,7 +1711,17 @@
"FILTER_INTEGRATIONS": "Filtruj integracje",
"SEARCH_INTEGRATIONS": "Integracje wyszukiwania",
"PAID": "Opłacone",
"INTEGRATION": "Integracja"
"INTEGRATION": "Integracja",
"MAKE_PAGE": {
"PROCESSING_AUTHORIZATION": "Przetwarzanie autoryzacji Make.com...",
"ERRORS": {
"AUTHORIZATION_FAILED": "Autoryzacja Make.com nie powiodła się",
"INVALID_CALLBACK": "Nieprawidłowe parametry wywołania zwrotnego"
},
"SUCCESS": {
"AUTHORIZATION_COMPLETE": "Autoryzacja Make.com zakończona pomyślnie"
}
}
},
"DASHBOARD_PAGE": {
"ACCOUNTING": "Rachunkowość",
+127 -14
View File
@@ -1294,16 +1294,93 @@
},
"MAKE_COM_PAGE": {
"SCENARIOS": "Cenários",
"EXECUTIONS": "Execuções",
"HISTORY": "História",
"WEBHOOKS": "Webhooks",
"WEBHOOKS_REFRESH": "Atualizar webhooks",
"WEBHOOK_ENABLE": "Ativar webhook",
"WEBHOOK_DISABLE": "Desativar webhook",
"WEBHOOK_PING": "Testar webhook",
"SCENARIO_ACTIVE": "Ativo",
"SCENARIO_INACTIVE": "Inativo",
"SCENARIO_LAST_EDITED": "Última edição: {{value}}",
"SCENARIO_DISABLE": "Desativar cenário",
"SCENARIO_ENABLE": "Ativar cenário",
"SCENARIO_RUN_ONCE": "Executar cenário uma vez",
"WEBHOOK_ENABLED": "Ativado",
"WEBHOOK_DISABLED": "Desativado",
"WEBHOOK_TYPE": "Tipo",
"WEBHOOK_QUEUE": "Fila",
"TEMPLATE_PUBLIC": "Público",
"TEMPLATE_PUBLISHED": "Publicado",
"REFRESH_CONNECTIONS": "Atualizar conexões",
"CONFIGURATION_TITLE": "Configuração do Make.com",
"ZONE_LABEL": "Zona",
"ZONE_HINT": "Selecione a zona que corresponde à sua conta Make.com (ex. us2.make.com)",
"ORGANIZATION_LABEL": "Organização Make.com",
"LOADING_ORGANIZATIONS": "Carregando organizações...",
"NO_ORGANIZATIONS": "Nenhuma organização encontrada",
"TEAM_LABEL": "Equipe Make.com",
"LOADING_TEAMS": "Carregando equipes...",
"NO_TEAMS": "Nenhuma equipe encontrada",
"CONNECTED_TO": "Conectado a {{org}} / {{team}} em {{zone}}",
"WEBHOOK_SETTINGS_TITLE": "Configurações do Webhook",
"CONNECTIONS": "Conexões",
"TEMPLATES": "Modelos",
"SETTINGS": "Configurações",
"ERRORS": {
"INVALID_FORM": "Por favor, preencha todos os campos obrigatórios",
"START_AUTHORIZATION": "Por favor, inicie o processo de autorização"
"CONNECT_TO_MAKE_COM": "Conectar ao Make.com",
"CONNECT_INFO": "Clique no botão abaixo para conectar sua conta Make.com. Você será redirecionado para o Make.com para autorizar a integração.",
"NO_SCENARIOS": "Nenhum cenário encontrado. Crie cenários no seu painel Make.com.",
"NO_HOOKS": "Nenhum webhook encontrado. Crie webhooks no seu painel Make.com.",
"NO_CONNECTIONS": "Nenhuma conexão encontrada. Crie conexões no seu painel Make.com.",
"NO_TEMPLATES": "Nenhum modelo encontrado.",
"TEST_CONNECTION": "Testar conexão",
"CONNECTION": {
"ACCOUNT": "Conta: {{value}}",
"TYPE": "Tipo: {{value}}",
"PACKAGE": "Pacote: {{value}}",
"EXPIRES": "Expira: {{value}}"
},
"CLIENT_ID": "ID do Cliente",
"CLIENT_SECRET": "Segredo do Cliente",
"NEXT_STEP_INFO": "Após a autorização, você será redirecionado para o Make.com para completar a configuração da integração"
"ERRORS": {
"INVALID_FORM": "Por favor preencha todos os campos obrigatórios",
"START_AUTHORIZATION": "Falha ao iniciar o processo de autorização",
"NO_ORGANIZATION": "Por favor selecione uma organização primeiro",
"LOAD_SETTINGS": "Falha ao carregar as configurações do Make.com",
"SAVE_SETTINGS": "Falha ao salvar as configurações do Make.com",
"LOAD_SETUP_STATUS": "Falha ao carregar o status de configuração",
"LOAD_SCENARIOS": "Falha ao carregar cenários",
"TOGGLE_SCENARIO": "Falha ao alternar cenário",
"RUN_SCENARIO": "Falha ao executar cenário",
"LOAD_CONNECTIONS": "Falha ao carregar conexões",
"TEST_CONNECTION": "Falha ao testar conexão",
"LOAD_WEBHOOKS": "Falha ao carregar webhooks",
"TOGGLE_WEBHOOK": "Falha ao alternar webhook",
"PING_WEBHOOK": "Falha ao testar webhook",
"SET_ZONE": "Falha ao definir zona",
"LOAD_ORGANIZATIONS": "Falha ao carregar organizações",
"SELECT_ORGANIZATION": "Falha ao selecionar organização",
"LOAD_TEAMS": "Falha ao carregar equipes",
"SELECT_TEAM": "Falha ao selecionar equipe",
"LOAD_TEMPLATES": "Falha ao carregar modelos",
"REQUEST_TIMEOUT": "A solicitação expirou. Por favor, tente novamente."
},
"SUCCESS": {
"SETTINGS_SAVED": "Configurações do Make.com salvas com sucesso",
"SCENARIO_ACTIVATED": "Cenário ativado",
"SCENARIO_DEACTIVATED": "Cenário desativado",
"SCENARIO_EXECUTED": "Cenário executado",
"CONNECTION_VALID": "A conexão \"{{name}}\" é válida",
"WEBHOOK_TOGGLED_ON": "Webhook ativado",
"WEBHOOK_TOGGLED_OFF": "Webhook desativado",
"WEBHOOK_PINGED": "Webhook \"{{name}}\" testado com sucesso",
"ZONE_SET": "Zona definida como {{zone}}",
"ORGANIZATION_SELECTED": "Organização \"{{name}}\" selecionada",
"TEAM_SELECTED": "Equipe \"{{name}}\" selecionada"
},
"SETUP_REQUIRED": {
"AUTHORIZE": "Por favor autorize o Make.com primeiro. Vá para a aba Configurações para conectar sua conta.",
"ZONE": "Por favor selecione sua zona do Make.com na aba Configurações.",
"ORGANIZATION": "Por favor selecione sua organização do Make.com na aba Configurações.",
"TEAM": "Por favor selecione sua equipe do Make.com na aba Configurações."
}
},
"MAKE_COM": {
"SETTINGS": {
@@ -1316,26 +1393,32 @@
}
},
"ZAPIER_PAGE": {
"CLIENT_ID": "ID do Cliente",
"CLIENT_SECRET": "Segredo do Cliente",
"NEXT_STEP_INFO": "Após a autorização, você será redirecionado para o Zapier para completar a configuração da integração",
"CONNECT_TO_ZAPIER": "Conectar ao Zapier",
"CONNECT_INFO": "Clique no botão abaixo para conectar sua conta Zapier. Você será redirecionado ao Zapier para autorizar a integração.",
"ERRORS": {
"LOAD_SETTINGS": "Falha ao carregar configurações do Zapier",
"SAVE_SETTINGS": "Falha ao salvar configurações do Zapier",
"INVALID_FORM": "Por favor, preencha todos os campos obrigatórios",
"MISSING_CREDENTIALS": "Credenciais do cliente ausentes",
"MISSING_CREDENTIALS": "A integração do Zapier não está configurada no servidor",
"NO_ORGANIZATION": "Por favor, selecione uma organização primeiro",
"NO_ACCESS_TOKEN": "Nenhum token de acesso disponível",
"LOAD_WEBHOOKS": "Falha ao carregar webhooks",
"DELETE_WEBHOOK": "Falha ao excluir webhook",
"LOAD_ACTIONS": "Falha ao carregar ações",
"LOAD_TRIGGERS": "Falha ao carregar gatilhos",
"LOAD_ZAPS": "Falha ao carregar zaps",
"LOAD_ZAP_TEMPLATES": "Falha ao carregar modelos de zap",
"CREATE_ZAP": "Falha ao criar zap",
"ZAP_TITLE_REQUIRED": "Por favor, forneça um título para o novo zap",
"NO_INTEGRATION_ID": "Nenhum ID de integração do Zapier foi fornecido",
"START_AUTHORIZATION": "Por favor, inicie o processo de autorização",
"INVALID_TOKEN": "Token inválido fornecido",
"TOKEN_NOT_FOUND": "Token não encontrado"
},
"SUCCESS": {
"SETTINGS_SAVED": "Configurações do Zapier salvas com sucesso",
"DELETE_WEBHOOK": "Webhook excluído com sucesso"
"DELETE_WEBHOOK": "Webhook excluído com sucesso",
"CREATE_ZAP": "Zap criado com sucesso"
},
"TAB": {
"TRIGGERS": "Gatilhos",
@@ -1377,6 +1460,26 @@
"WEBHOOKS": {
"TITLE": "Webhooks",
"NO_WEBHOOKS": "Nenhum webhook configurado"
},
"ZAPS": {
"TITLE": "Zaps",
"NO_ZAPS": "Nenhum zap disponível",
"CREATE": "Criar Zap",
"SUBMIT": "Criar",
"ADD_STEP": "Adicionar etapa",
"OPEN_EDITOR": "Abrir no Zapier",
"ENABLED": "Habilitado",
"DISABLED": "Desabilitado",
"TITLE_PLACEHOLDER": "Insira um título para o novo Zap",
"FIELDS": {
"TITLE": "Título",
"STEPS": "Etapas",
"STEP": "Etapa",
"STEP_TITLE": "Título da etapa",
"ACTION": "Ação",
"AUTHENTICATION": "Autenticação",
"INPUTS": "Entradas (JSON)"
}
}
},
"GAUZY_AI_PAGE": {
@@ -1609,7 +1712,17 @@
"FILTER_INTEGRATIONS": "Integrações de filtro",
"SEARCH_INTEGRATIONS": "Integrações de busca",
"PAID": "Pago",
"INTEGRATION": "Integração"
"INTEGRATION": "Integração",
"MAKE_PAGE": {
"PROCESSING_AUTHORIZATION": "Processando autorização do Make.com...",
"ERRORS": {
"AUTHORIZATION_FAILED": "Autorização do Make.com falhou",
"INVALID_CALLBACK": "Parâmetros de retorno inválidos"
},
"SUCCESS": {
"AUTHORIZATION_COMPLETE": "Autorização do Make.com concluída com sucesso"
}
}
},
"DASHBOARD_PAGE": {
"ACCOUNTING": "Contabilidade",
+125 -12
View File
@@ -1302,16 +1302,93 @@
},
"MAKE_COM_PAGE": {
"SCENARIOS": "Сценарии",
"EXECUTIONS": "Выполнения",
"HISTORY": "История",
"WEBHOOKS": "Вебхуки",
"WEBHOOKS_REFRESH": "Обновить вебхуки",
"WEBHOOK_ENABLE": "Включить вебхук",
"WEBHOOK_DISABLE": "Отключить вебхук",
"WEBHOOK_PING": "Тестировать вебхук",
"SCENARIO_ACTIVE": "Активный",
"SCENARIO_INACTIVE": "Неактивный",
"SCENARIO_LAST_EDITED": "Последнее редактирование: {{value}}",
"SCENARIO_DISABLE": "Отключить сценарий",
"SCENARIO_ENABLE": "Включить сценарий",
"SCENARIO_RUN_ONCE": "Запустить сценарий один раз",
"WEBHOOK_ENABLED": "Включен",
"WEBHOOK_DISABLED": "Отключен",
"WEBHOOK_TYPE": "Тип",
"WEBHOOK_QUEUE": "Очередь",
"TEMPLATE_PUBLIC": "Публичный",
"TEMPLATE_PUBLISHED": "Опубликованный",
"REFRESH_CONNECTIONS": "Обновить подключения",
"CONFIGURATION_TITLE": "Конфигурация Make.com",
"ZONE_LABEL": "Зона",
"ZONE_HINT": "Выберите зону, соответствующую вашей учетной записи Make.com (например, us2.make.com)",
"ORGANIZATION_LABEL": "Организация Make.com",
"LOADING_ORGANIZATIONS": "Загрузка организаций...",
"NO_ORGANIZATIONS": "Организации не найдены",
"TEAM_LABEL": "Команда Make.com",
"LOADING_TEAMS": "Загрузка команд...",
"NO_TEAMS": "Команды не найдены",
"CONNECTED_TO": "Подключено к {{org}} / {{team}} на {{zone}}",
"WEBHOOK_SETTINGS_TITLE": "Настройки вебхука",
"CONNECTIONS": "Подключения",
"TEMPLATES": "Шаблоны",
"SETTINGS": "Настройки",
"CONNECT_TO_MAKE_COM": "Подключиться к Make.com",
"CONNECT_INFO": "Нажмите кнопку ниже, чтобы подключить вашу учётную запись Make.com. Вы будете перенаправлены на Make.com для авторизации интеграции.",
"NO_SCENARIOS": "Сценарии не найдены. Создайте сценарии в панели управления Make.com.",
"NO_HOOKS": "Вебхуки не найдены. Создайте вебхуки в панели управления Make.com.",
"NO_CONNECTIONS": "Подключения не найдены. Создайте подключения в панели управления Make.com.",
"NO_TEMPLATES": "Шаблоны не найдены.",
"TEST_CONNECTION": "Проверить подключение",
"CONNECTION": {
"ACCOUNT": "Аккаунт: {{value}}",
"TYPE": "Тип: {{value}}",
"PACKAGE": "Пакет: {{value}}",
"EXPIRES": "Истекает: {{value}}"
},
"ERRORS": {
"INVALID_FORM": "Пожалуйста, заполните все обязательные поля",
"START_AUTHORIZATION": "Пожалуйста, начните процесс авторизации"
"START_AUTHORIZATION": "Не удалось начать процесс авторизации",
"NO_ORGANIZATION": "Пожалуйста, сначала выберите организацию",
"LOAD_SETTINGS": "Не удалось загрузить настройки Make.com",
"SAVE_SETTINGS": "Не удалось сохранить настройки Make.com",
"LOAD_SETUP_STATUS": "Не удалось загрузить статус настройки",
"LOAD_SCENARIOS": "Не удалось загрузить сценарии",
"TOGGLE_SCENARIO": "Не удалось переключить сценарий",
"RUN_SCENARIO": "Не удалось запустить сценарий",
"LOAD_CONNECTIONS": "Не удалось загрузить подключения",
"TEST_CONNECTION": "Не удалось проверить подключение",
"LOAD_WEBHOOKS": "Не удалось загрузить вебхуки",
"TOGGLE_WEBHOOK": "Не удалось переключить вебхук",
"PING_WEBHOOK": "Не удалось протестировать вебхук",
"SET_ZONE": "Не удалось установить зону",
"LOAD_ORGANIZATIONS": "Не удалось загрузить организации",
"SELECT_ORGANIZATION": "Не удалось выбрать организацию",
"LOAD_TEAMS": "Не удалось загрузить команды",
"SELECT_TEAM": "Не удалось выбрать команду",
"LOAD_TEMPLATES": "Не удалось загрузить шаблоны",
"REQUEST_TIMEOUT": "Время запроса истекло. Пожалуйста, попробуйте снова."
},
"CLIENT_ID": "ID клиента",
"CLIENT_SECRET": "Секрет клиента",
"NEXT_STEP_INFO": "После авторизации вы будете перенаправлены на Make.com для завершения настройки интеграции"
"SUCCESS": {
"SETTINGS_SAVED": "Настройки Make.com успешно сохранены",
"SCENARIO_ACTIVATED": "Сценарий активирован",
"SCENARIO_DEACTIVATED": "Сценарий деактивирован",
"SCENARIO_EXECUTED": "Сценарий выполнен",
"CONNECTION_VALID": "Подключение \"{{name}}\" действительно",
"WEBHOOK_TOGGLED_ON": "Вебхук включен",
"WEBHOOK_TOGGLED_OFF": "Вебхук отключен",
"WEBHOOK_PINGED": "Вебхук \"{{name}}\" успешно протестирован",
"ZONE_SET": "Зона установлена на {{zone}}",
"ORGANIZATION_SELECTED": "Выбрана организация \"{{name}}\"",
"TEAM_SELECTED": "Выбрана команда \"{{name}}\""
},
"SETUP_REQUIRED": {
"AUTHORIZE": "Сначала авторизуйтесь в Make.com. Перейдите на вкладку Настройки, чтобы подключить аккаунт.",
"ZONE": "Выберите зону Make.com на вкладке Настройки.",
"ORGANIZATION": "Выберите организацию Make.com на вкладке Настройки.",
"TEAM": "Выберите команду Make.com на вкладке Настройки."
}
},
"MAKE_COM": {
"SETTINGS": {
@@ -1324,26 +1401,32 @@
}
},
"ZAPIER_PAGE": {
"CLIENT_ID": "ID клиента",
"CLIENT_SECRET": "Секрет клиента",
"NEXT_STEP_INFO": "После авторизации вы будете перенаправлены на Zapier для завершения настройки интеграции",
"CONNECT_TO_ZAPIER": "Подключиться к Zapier",
"CONNECT_INFO": "Нажмите кнопку ниже, чтобы подключить вашу учетную запись Zapier. Вы будете перенаправлены на Zapier для авторизации интеграции.",
"ERRORS": {
"LOAD_SETTINGS": "Не удалось загрузить настройки Zapier",
"SAVE_SETTINGS": "Не удалось сохранить настройки Zapier",
"INVALID_FORM": "Пожалуйста, заполните все обязательные поля",
"MISSING_CREDENTIALS": "Отсутствуют учетные данные клиента",
"NO_ORGANIZATION": "Пожалуйста, сначала выберите организацию",
"NO_ACCESS_TOKEN": "Нет доступного токена доступа",
"LOAD_WEBHOOKS": "Не удалось загрузить вебхуки",
"DELETE_WEBHOOK": "Не удалось удалить вебхук",
"LOAD_ACTIONS": "Не удалось загрузить действия",
"LOAD_TRIGGERS": "Не удалось загрузить триггеры",
"LOAD_ZAPS": "Не удалось загрузить запы",
"LOAD_ZAP_TEMPLATES": "Не удалось загрузить шаблоны зап",
"CREATE_ZAP": "Не удалось создать зап",
"ZAP_TITLE_REQUIRED": "Пожалуйста, укажите название для нового запа",
"NO_INTEGRATION_ID": "Не был предоставлен идентификатор интеграции Zapier",
"START_AUTHORIZATION": "Пожалуйста, начните процесс авторизации",
"INVALID_TOKEN": "Предоставлен недействительный токен",
"TOKEN_NOT_FOUND": "Токен не найден"
},
"SUCCESS": {
"SETTINGS_SAVED": "Настройки Zapier успешно сохранены",
"DELETE_WEBHOOK": "Вебхук успешно удален"
"DELETE_WEBHOOK": "Вебхук успешно удален",
"CREATE_ZAP": "Зап успешно создан"
},
"TAB": {
"TRIGGERS": "Триггеры",
@@ -1385,6 +1468,26 @@
"WEBHOOKS": {
"TITLE": "Вебхуки",
"NO_WEBHOOKS": "Нет настроенных вебхуков"
},
"ZAPS": {
"TITLE": "Запы",
"NO_ZAPS": "Нет доступных запов",
"CREATE": "Создать Зап",
"SUBMIT": "Создать",
"ADD_STEP": "Добавить шаг",
"OPEN_EDITOR": "Открыть в Zapier",
"ENABLED": "Включено",
"DISABLED": "Отключено",
"TITLE_PLACEHOLDER": "Введите название для нового Запа",
"FIELDS": {
"TITLE": "Название",
"STEPS": "Шаги",
"STEP": "Шаг",
"STEP_TITLE": "Название шага",
"ACTION": "Действие",
"AUTHENTICATION": "Аутентификация",
"INPUTS": "Входные данные (JSON)"
}
}
},
"GAUZY_AI_PAGE": {
@@ -1659,8 +1762,18 @@
"INTEGRATION_DISABLED": "Интеграция '{{provider}}' успешно отключена",
"INTEGRATION_ADDED": "Интеграция '{{provider}}' добавлена ​​в '{{organization}}'"
},
"ENABLED": "Enabled",
"DISABLED": "Disabled"
"ENABLED": "Включено",
"DISABLED": "Отключено",
"MAKE_PAGE": {
"PROCESSING_AUTHORIZATION": "Обработка авторизации Make.com...",
"ERRORS": {
"AUTHORIZATION_FAILED": "Авторизация Make.com не удалась",
"INVALID_CALLBACK": "Недопустимые параметры обратного вызова"
},
"SUCCESS": {
"AUTHORIZATION_COMPLETE": "Авторизация Make.com успешно завершена"
}
}
},
"DASHBOARD_PAGE": {
"ACCOUNTING": "Бухгалтер",
+126 -13
View File
@@ -1294,16 +1294,93 @@
},
"MAKE_COM_PAGE": {
"SCENARIOS": "场景",
"EXECUTIONS": "执行",
"HISTORY": "历史",
"WEBHOOKS": "Webhooks",
"WEBHOOKS_REFRESH": "刷新 Webhooks",
"WEBHOOK_ENABLE": "启用 Webhook",
"WEBHOOK_DISABLE": "禁用 Webhook",
"WEBHOOK_PING": "测试 Webhook",
"SCENARIO_ACTIVE": "活跃",
"SCENARIO_INACTIVE": "非活跃",
"SCENARIO_LAST_EDITED": "最后编辑:{{value}}",
"SCENARIO_DISABLE": "禁用场景",
"SCENARIO_ENABLE": "启用场景",
"SCENARIO_RUN_ONCE": "运行场景一次",
"WEBHOOK_ENABLED": "已启用",
"WEBHOOK_DISABLED": "已禁用",
"WEBHOOK_TYPE": "类型",
"WEBHOOK_QUEUE": "队列",
"TEMPLATE_PUBLIC": "公开",
"TEMPLATE_PUBLISHED": "已发布",
"REFRESH_CONNECTIONS": "刷新连接",
"CONFIGURATION_TITLE": "Make.com 配置",
"ZONE_LABEL": "区域",
"ZONE_HINT": "选择与您的 Make.com 帐户匹配的区域(例如 us2.make.com)",
"ORGANIZATION_LABEL": "Make.com 组织",
"LOADING_ORGANIZATIONS": "正在加载组织...",
"NO_ORGANIZATIONS": "未找到组织",
"TEAM_LABEL": "Make.com 团队",
"LOADING_TEAMS": "正在加载团队...",
"NO_TEAMS": "未找到团队",
"CONNECTED_TO": "已连接到 {{org}} / {{team}} 在 {{zone}}",
"WEBHOOK_SETTINGS_TITLE": "Webhook 设置",
"CONNECTIONS": "连接",
"TEMPLATES": "模板",
"SETTINGS": "设置",
"CONNECT_TO_MAKE_COM": "连接到 Make.com",
"CONNECT_INFO": "点击下面的按钮连接您的 Make.com 帐户。您将被重定向到 Make.com 以授权集成。",
"NO_SCENARIOS": "未找到场景。在您的 Make.com 仪表板中创建场景。",
"NO_HOOKS": "未找到 Webhooks。在您的 Make.com 仪表板中创建 Webhooks。",
"NO_CONNECTIONS": "未找到连接。在您的 Make.com 仪表板中创建连接。",
"NO_TEMPLATES": "未找到模板。",
"TEST_CONNECTION": "测试连接",
"CONNECTION": {
"ACCOUNT": "账户:{{value}}",
"TYPE": "类型:{{value}}",
"PACKAGE": "套餐:{{value}}",
"EXPIRES": "过期:{{value}}"
},
"ERRORS": {
"INVALID_FORM": "请填写所有必填字段",
"START_AUTHORIZATION": "请开始授权过程"
"START_AUTHORIZATION": "启动授权过程失败",
"NO_ORGANIZATION": "请先选择一个组织",
"LOAD_SETTINGS": "加载 Make.com 设置失败",
"SAVE_SETTINGS": "保存 Make.com 设置失败",
"LOAD_SETUP_STATUS": "加载设置状态失败",
"LOAD_SCENARIOS": "加载场景失败",
"TOGGLE_SCENARIO": "切换场景失败",
"RUN_SCENARIO": "运行场景失败",
"LOAD_CONNECTIONS": "加载连接失败",
"TEST_CONNECTION": "测试连接失败",
"LOAD_WEBHOOKS": "加载 Webhooks 失败",
"TOGGLE_WEBHOOK": "切换 Webhook 失败",
"PING_WEBHOOK": "测试 Webhook 失败",
"SET_ZONE": "设置区域失败",
"LOAD_ORGANIZATIONS": "加载组织失败",
"SELECT_ORGANIZATION": "选择组织失败",
"LOAD_TEAMS": "加载团队失败",
"SELECT_TEAM": "选择团队失败",
"LOAD_TEMPLATES": "加载模板失败",
"REQUEST_TIMEOUT": "请求超时,请重试。"
},
"CLIENT_ID": "客户端ID",
"CLIENT_SECRET": "客户端密钥",
"NEXT_STEP_INFO": "授权后,您将被重定向到Make.com以完成集成设置"
"SUCCESS": {
"SETTINGS_SAVED": "Make.com 设置已成功保存",
"SCENARIO_ACTIVATED": "场景已激活",
"SCENARIO_DEACTIVATED": "场景已停用",
"SCENARIO_EXECUTED": "场景已执行",
"CONNECTION_VALID": "连接 \"{{name}}\" 有效",
"WEBHOOK_TOGGLED_ON": "Webhook 已启用",
"WEBHOOK_TOGGLED_OFF": "Webhook 已禁用",
"WEBHOOK_PINGED": "Webhook \"{{name}}\" 测试成功",
"ZONE_SET": "区域已设置为 {{zone}}",
"ORGANIZATION_SELECTED": "已选择组织 \"{{name}}\"",
"TEAM_SELECTED": "已选择团队 \"{{name}}\""
},
"SETUP_REQUIRED": {
"AUTHORIZE": "请先授权 Make.com。转到设置选项卡连接您的帐户。",
"ZONE": "请在设置选项卡中选择您的 Make.com 区域。",
"ORGANIZATION": "请在设置选项卡中选择您的 Make.com 组织。",
"TEAM": "请在设置选项卡中选择您的 Make.com 团队。"
}
},
"MAKE_COM": {
"SETTINGS": {
@@ -1316,26 +1393,32 @@
}
},
"ZAPIER_PAGE": {
"CLIENT_ID": "客户端ID",
"CLIENT_SECRET": "客户端密钥",
"NEXT_STEP_INFO": "授权后,您将被重定向到Zapier以完成集成设置",
"CONNECT_TO_ZAPIER": "连接到 Zapier",
"CONNECT_INFO": "点击下方按钮连接您的 Zapier 账户。您将被重定向到 Zapier 以授权集成。",
"ERRORS": {
"LOAD_SETTINGS": "加载Zapier设置失败",
"SAVE_SETTINGS": "保存Zapier设置失败",
"INVALID_FORM": "请填写所有必填字段",
"MISSING_CREDENTIALS": "缺少客户端凭据",
"NO_ORGANIZATION": "请先选择一个组织",
"NO_ACCESS_TOKEN": "没有可用的访问令牌",
"LOAD_WEBHOOKS": "加载Webhooks失败",
"DELETE_WEBHOOK": "删除Webhook失败",
"LOAD_ACTIONS": "加载动作失败",
"LOAD_TRIGGERS": "加载触发器失败",
"LOAD_ZAPS": "加载zaps失败",
"LOAD_ZAP_TEMPLATES": "加载zap模板失败",
"CREATE_ZAP": "创建zap失败",
"ZAP_TITLE_REQUIRED": "请为新zap提供标题",
"NO_INTEGRATION_ID": "未提供Zapier集成ID",
"START_AUTHORIZATION": "请开始授权过程",
"INVALID_TOKEN": "提供的令牌无效",
"TOKEN_NOT_FOUND": "未找到令牌"
},
"SUCCESS": {
"SETTINGS_SAVED": "Zapier设置保存成功",
"DELETE_WEBHOOK": "Webhook删除成功"
"DELETE_WEBHOOK": "Webhook删除成功",
"CREATE_ZAP": "Zap创建成功"
},
"TAB": {
"TRIGGERS": "触发器",
@@ -1377,6 +1460,26 @@
"WEBHOOKS": {
"TITLE": "Webhooks",
"NO_WEBHOOKS": "未配置Webhooks"
},
"ZAPS": {
"TITLE": "Zaps",
"NO_ZAPS": "没有可用的Zaps",
"CREATE": "创建Zap",
"SUBMIT": "创建",
"ADD_STEP": "添加步骤",
"OPEN_EDITOR": "在Zapier中打开",
"ENABLED": "已启用",
"DISABLED": "已禁用",
"TITLE_PLACEHOLDER": "输入新Zap的标题",
"FIELDS": {
"TITLE": "标题",
"STEPS": "步骤",
"STEP": "步骤",
"STEP_TITLE": "步骤标题",
"ACTION": "操作",
"AUTHENTICATION": "身份验证",
"INPUTS": "输入 (JSON)"
}
}
},
"GAUZY_AI_PAGE": {
@@ -1609,7 +1712,17 @@
"FILTER_INTEGRATIONS": "筛选集成",
"SEARCH_INTEGRATIONS": "搜索集成",
"PAID": "支付",
"INTEGRATION": "整合"
"INTEGRATION": "整合",
"MAKE_PAGE": {
"PROCESSING_AUTHORIZATION": "正在处理 Make.com 授权...",
"ERRORS": {
"AUTHORIZATION_FAILED": "Make.com 授权失败",
"INVALID_CALLBACK": "无效的回调参数"
},
"SUCCESS": {
"AUTHORIZATION_COMPLETE": "Make.com 授权已成功完成"
}
}
},
"DASHBOARD_PAGE": {
"ACCOUNTING": "会计",
@@ -4473,8 +4586,8 @@
"DB_CLEANUP_KEEP": "保留",
"DB_CLEANUP_UNSYNCED_TITLE": "检测到未同步数据",
"DB_CLEANUP_UNSYNCED_MESSAGE": "警告:可能丢失数据",
"DB_CLEANUP_UNSYNCED_DETAIL": "您有 {{countsText}}。删除数据库将永久清除这些离线数据。如果您不确定,请点击\u201c保留\u201d。",
"DB_CLEANUP_UNKNOWN_DETAIL": "我们无法确定您是否有未同步的离线数据。删除数据库可能会永久清除未保存的工作。如果您不确定,请点击\u201c保留\u201d。",
"DB_CLEANUP_UNSYNCED_DETAIL": "您有 {{countsText}}。删除数据库将永久清除这些离线数据。如果您不确定,请点击“保留”。",
"DB_CLEANUP_UNKNOWN_DETAIL": "我们无法确定您是否有未同步的离线数据。删除数据库可能会永久清除未保存的工作。如果您不确定,请点击“保留”。",
"UNSYNCED_TIMERS_COUNT": "{{count}} unsynced timer(s)",
"UNSYNCED_INTERVALS_COUNT": "{{count}} unsynced interval(s)",
"AND_JOIN": "and"
+13 -13
View File
@@ -44403,10 +44403,10 @@ yoctocolors@^2.1.1:
resolved "https://registry.yarnpkg.com/yoctocolors/-/yoctocolors-2.1.2.tgz#d795f54d173494e7d8db93150cec0ed7f678c83a"
integrity sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==
zapier-platform-cli@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/zapier-platform-cli/-/zapier-platform-cli-18.2.0.tgz#e389ea17a3d8816b2645f3cce76b07b18b856e44"
integrity sha512-HhqZ2lwVv7qxh791h5kJzadeZlbN2wTjVSf0LUvSVyE1ZbVXwP5IXchv0uatH10CyueizfUExfPlAH3ilNGUog==
zapier-platform-cli@^18.3.0:
version "18.3.0"
resolved "https://registry.yarnpkg.com/zapier-platform-cli/-/zapier-platform-cli-18.3.0.tgz#a99a654ecc5e99f86c0a9466a25992be411568a6"
integrity sha512-XKtrhOgfrVuY8MeCYA3mpqKe/tCg996r0mbgi9FV20aT/divPTMvk2IIHspUZSvSYBQJiDF5SSwD5CprBN9/5w==
dependencies:
"@oclif/core" "4.5.2"
"@oclif/plugin-autocomplete" "3.2.34"
@@ -44446,10 +44446,10 @@ zapier-platform-cli@18.2.0:
yeoman-environment "4.4.3"
yeoman-generator "7.5.1"
zapier-platform-core@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/zapier-platform-core/-/zapier-platform-core-18.2.0.tgz#4b0c5193d6a732f3157b20e51047cf840adeed59"
integrity sha512-adC07pEj4mqxneq3eOqbT2aTSqyUVlzLYQWp5kbA3P5tSYZs/OdovRYoFzNyXCu/UplcmKgeXxjQPIiikEZXUQ==
zapier-platform-core@^18.3.0:
version "18.3.0"
resolved "https://registry.yarnpkg.com/zapier-platform-core/-/zapier-platform-core-18.3.0.tgz#e13547af6ffdd0b956f0b37d22efe1736cef8e92"
integrity sha512-qhvWPLqGd29o4SnAE4Ve9K6BLeCUjko16pdxqK9KNzjDH5e57RLOqFICuXS+0DATcKirdqUCoodxnMTHahk/XA==
dependencies:
"@zapier/secret-scrubber" "^1.1.2"
content-disposition "0.5.4"
@@ -44462,14 +44462,14 @@ zapier-platform-core@18.2.0:
node-fetch "2.7.0"
oauth-sign "0.9.0"
semver "7.7.2"
zapier-platform-schema "18.2.0"
zapier-platform-schema "18.3.0"
optionalDependencies:
"@types/node" "^20.3.1"
zapier-platform-schema@18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/zapier-platform-schema/-/zapier-platform-schema-18.2.0.tgz#832055ea338d48576c3493ecb97081d6f4fafda2"
integrity sha512-Iga89Fy1UgJZNMsTkd6QQmb0yBv8YgSjU9QUP6tyt1E4PX1R7XMlUc/7L6AaP8Nmuw9kaDu1fURiuWqoTDfsAg==
zapier-platform-schema@18.3.0:
version "18.3.0"
resolved "https://registry.yarnpkg.com/zapier-platform-schema/-/zapier-platform-schema-18.3.0.tgz#384f733535fe2d9cf4b3761452a18db2e8f3e67a"
integrity sha512-WigaXQyHGowxerFO6lfF44RvgmdFbTIHRWus7AeVT+3bQdN+rJeadlTM9/gTrkmrgcbK3dQaoi4OuNsoASf3Cg==
dependencies:
jsonschema "1.5.0"
lodash "4.17.23"