Skip to content

Commit b2b1f61

Browse files
committed
feat: add custom webhook
1 parent 4a7f1a7 commit b2b1f61

File tree

2 files changed

+83
-21
lines changed

2 files changed

+83
-21
lines changed

src/components/flow/routers/webhook/WebhookRouterForm.module.scss

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,34 @@
9898
margin-left: 10px;
9999
}
100100

101+
.custom_function_wrapper {
102+
position: relative;
103+
width: 100%;
104+
105+
.toggle_icon {
106+
position: absolute;
107+
right: 12px;
108+
top: 50%;
109+
transform: translateY(-50%);
110+
cursor: pointer;
111+
color: #999;
112+
font-size: 14px;
113+
font-weight: 200;
114+
font-family: Arial, sans-serif;
115+
116+
line-height: 1;
117+
z-index: 10;
118+
display: flex;
119+
align-items: center;
120+
justify-content: center;
121+
user-select: none;
122+
123+
&:hover {
124+
color: #666;
125+
}
126+
}
127+
}
128+
101129
.body_form {
102130
.req_body {
103131
margin-top: 8px;
@@ -106,4 +134,4 @@
106134
--temba-textinput-font-size: 0.8em;
107135
--unicode-bidi: embed;
108136
}
109-
}
137+
}

src/components/flow/routers/webhook/WebhookRouterForm.tsx

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,21 @@ export default class WebhookRouterForm extends React.Component<
9292

9393
if (this.state.method.value.value === Methods.FUNCTION && this.state.url.value) {
9494
const functionName = this.state.url.value;
95-
const selectedOption = webhookOptions.find((opt: any) => opt.name === functionName);
95+
let selectedOption = webhookOptions.find((opt: any) => opt.name === functionName);
96+
if (!selectedOption) {
97+
selectedOption = {
98+
value: 'custom',
99+
name: 'custom',
100+
label: 'Custom'
101+
};
102+
}
96103

97104
this.setState({
98105
webhookOptions,
99-
webhookFunction: { value: selectedOption || null }
106+
webhookFunction: { value: selectedOption || null },
107+
url: {
108+
value: selectedOption.value === 'custom' ? this.state.url.value : selectedOption.name
109+
}
100110
});
101111
} else {
102112
this.setState({ webhookOptions });
@@ -109,15 +119,18 @@ export default class WebhookRouterForm extends React.Component<
109119
}
110120

111121
private handleWebhookFunctionChanged(selected: any): boolean {
122+
const isCustom = selected?.value === 'custom';
112123
const prevFunction = this.state.webhookFunction?.value;
113124
const prevFunctionName = prevFunction?.name || prevFunction?.value;
114125

115126
let updates: Partial<WebhookRouterFormState> = {
116127
webhookFunction: { value: selected },
117-
url: { value: selected ? selected.name || selected.value : '' }
128+
url: {
129+
value: selected ? (isCustom ? '' : selected.name || selected.value) : ''
130+
}
118131
};
119132

120-
if (selected) {
133+
if (selected && !isCustom) {
121134
const backendDefaultBody = selected.body || '';
122135
const currentBody = this.state.body.value;
123136

@@ -126,6 +139,8 @@ export default class WebhookRouterForm extends React.Component<
126139
updates.body = {
127140
value: shouldResetBody ? backendDefaultBody : currentBody
128141
};
142+
} else if (isCustom) {
143+
updates.body = { value: '{}' };
129144
} else {
130145
updates.body = { value: '' };
131146
}
@@ -308,7 +323,6 @@ export default class WebhookRouterForm extends React.Component<
308323

309324
if (valid) {
310325
const payload = stateToNode(this.props.nodeSettings, this.state);
311-
312326
this.props.updateRouter(payload);
313327

314328
this.props.onClose(false);
@@ -423,21 +437,41 @@ export default class WebhookRouterForm extends React.Component<
423437
</div>
424438
<div className={styles.url}>
425439
{method === 'FUNCTION' ? (
426-
<TembaSelectElement
427-
key="webhook_function_select"
428-
name={i18n.t('forms.function', 'Function')}
429-
placeholder={
430-
this.state.isLoading
431-
? 'Loading functions…'
432-
: i18n.t('forms.select_or_type', 'Type to search or select')
433-
}
434-
entry={this.state.webhookFunction}
435-
searchable={true}
436-
multi={false}
437-
expressions={false}
438-
onChange={this.handleWebhookFunctionChanged}
439-
options={this.state.webhookOptions}
440-
/>
440+
<>
441+
{this.state.webhookFunction?.value?.value === 'custom' ? (
442+
<div className={styles.custom_function_wrapper}>
443+
<TextInputElement
444+
name={i18n.t('forms.custom_function_label', 'Function Name')}
445+
placeholder={i18n.t('forms.enter_label', 'Enter function name')}
446+
entry={this.state.url}
447+
onChange={(v: string) => this.handleUpdate({ url: v })}
448+
autocomplete={true}
449+
/>
450+
<div
451+
className={styles.toggle_icon}
452+
onClick={() => this.handleWebhookFunctionChanged(null)}
453+
>
454+
455+
</div>
456+
</div>
457+
) : (
458+
<TembaSelectElement
459+
key="webhook_function_select"
460+
name={i18n.t('forms.function', 'Function')}
461+
placeholder={
462+
this.state.isLoading
463+
? 'Loading functions…'
464+
: i18n.t('forms.select_or_type', 'Type to search or select')
465+
}
466+
entry={this.state.webhookFunction}
467+
searchable={true}
468+
multi={false}
469+
expressions={false}
470+
onChange={this.handleWebhookFunctionChanged}
471+
options={this.state.webhookOptions}
472+
/>
473+
)}
474+
</>
441475
) : (
442476
<TextInputElement
443477
name={i18n.t('forms.url', 'URL')}

0 commit comments

Comments
 (0)