Skip to content

Commit 49d5830

Browse files
committed
Add component based whatsapp templates
1 parent d5ca472 commit 49d5830

22 files changed

+222
-295
lines changed

src/components/flow/actions/localization/MsgLocalizationForm.tsx

+42-58
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
13
import { react as bindCallbacks } from 'auto-bind';
24
import Dialog, { ButtonSet, Tab } from 'components/dialog/Dialog';
35
import styles from 'components/flow/actions/action/Action.module.scss';
@@ -16,16 +18,16 @@ import { MaxOfTenItems, validate } from 'store/validators';
1618
import { initializeLocalizedForm } from './helpers';
1719
import i18n from 'config/i18n';
1820
import { Trans } from 'react-i18next';
19-
import { range } from 'utils';
2021
import { renderIssues } from '../helpers';
2122
import { Attachment, renderAttachments } from '../sendmsg/attachments';
2223
import { AxiosError, AxiosResponse } from 'axios';
24+
import { TembaComponent } from 'temba/TembaComponent';
2325

2426
export interface MsgLocalizationFormState extends FormState {
2527
message: StringEntry;
2628
quickReplies: StringArrayEntry;
2729
audio: StringEntry;
28-
templateVariables: StringEntry[];
30+
params: any;
2931
templating: MsgTemplating;
3032
attachments: Attachment[];
3133
uploadInProgress: boolean;
@@ -91,7 +93,7 @@ export default class MsgLocalizationForm extends React.Component<
9193
}
9294

9395
private handleSave(): void {
94-
const { message: text, quickReplies, audio, templateVariables, attachments } = this.state;
96+
const { message: text, quickReplies, audio, attachments } = this.state;
9597

9698
// make sure we are valid for saving, only quick replies can be invalid
9799
const typeConfig = determineTypeConfig(this.props.nodeSettings);
@@ -125,17 +127,22 @@ export default class MsgLocalizationForm extends React.Component<
125127
}
126128
];
127129

128-
// if we have template variables, they show up on their own key
129-
const hasTemplateVariables = templateVariables.find(
130-
(entry: StringEntry) => entry.value.length > 0
131-
);
132-
if (hasTemplateVariables) {
133-
localizations.push({
134-
uuid: this.state.templating.uuid,
135-
translations: { variables: templateVariables.map((entry: StringEntry) => entry.value) }
130+
// save our template components
131+
const templating = (this.props.nodeSettings.originalAction as SendMsg).templating;
132+
if (this.state.params && templating) {
133+
const components = templating.components;
134+
135+
// find the matching component for our params
136+
Object.keys(this.state.params).forEach((key: any) => {
137+
const component = components.find((c: any) => c.name === key);
138+
if (component) {
139+
localizations.push({
140+
uuid: component.uuid,
141+
translations: { params: this.state.params[key] }
142+
});
143+
}
136144
});
137145
}
138-
139146
this.props.updateLocalizations(this.props.language.id, localizations);
140147

141148
// notify our modal we are done
@@ -157,26 +164,20 @@ export default class MsgLocalizationForm extends React.Component<
157164
this.handleUpdate({ quickReplies });
158165
}
159166

160-
private handleTemplateVariableChanged(updatedText: string, num: number): void {
161-
const entry = validate(`Variable ${num + 1}`, updatedText, []);
162-
163-
const templateVariables = mutate(this.state.templateVariables, {
164-
$merge: { [num]: entry }
165-
}) as StringEntry[];
166-
167-
this.setState({ templateVariables });
167+
private handleTemplateVariableChanged(event: any): void {
168+
this.setState({ params: event.detail.params });
168169
}
169170

170171
private handleAttachmentUploading(isUploading: boolean) {
171-
const uploadError: string = '';
172+
const uploadError = '';
172173
console.log(uploadError);
173174
this.setState({ uploadError });
174175

175176
if (isUploading) {
176-
const uploadInProgress: boolean = true;
177+
const uploadInProgress = true;
177178
this.setState({ uploadInProgress });
178179
} else {
179-
const uploadInProgress: boolean = false;
180+
const uploadInProgress = false;
180181
this.setState({ uploadInProgress });
181182
}
182183
}
@@ -193,18 +194,18 @@ export default class MsgLocalizationForm extends React.Component<
193194
});
194195
this.setState({ attachments });
195196

196-
const uploadError: string = '';
197+
const uploadError = '';
197198
console.log(uploadError);
198199
this.setState({ uploadError });
199200
}
200201

201-
const uploadInProgress: boolean = false;
202+
const uploadInProgress = false;
202203
this.setState({ uploadInProgress });
203204
}
204205

205206
private handleAttachmentUploadFailed(error: AxiosError) {
206207
//nginx returns a 300+ if there's an error
207-
let uploadError: string = '';
208+
let uploadError = '';
208209
const status = error.response.status;
209210
if (status >= 500) {
210211
uploadError = i18n.t('file_upload_failed_generic', 'File upload failed, please try again');
@@ -215,7 +216,7 @@ export default class MsgLocalizationForm extends React.Component<
215216
}
216217
this.setState({ uploadError });
217218

218-
const uploadInProgress: boolean = false;
219+
const uploadInProgress = false;
219220
this.setState({ uploadInProgress });
220221
}
221222

@@ -249,16 +250,7 @@ export default class MsgLocalizationForm extends React.Component<
249250
const typeConfig = determineTypeConfig(this.props.nodeSettings);
250251
const tabs: Tab[] = [];
251252

252-
if (
253-
this.state.templating &&
254-
typeConfig.localizeableKeys!.indexOf('templating.variables') > -1
255-
) {
256-
const hasLocalizedValue = !!this.state.templateVariables.find(
257-
(entry: StringEntry) => entry.value.length > 0
258-
);
259-
260-
const variable = i18n.t('forms.variable', 'Variable');
261-
253+
if (this.state.templating) {
262254
tabs.push({
263255
name: 'WhatsApp',
264256
body: (
@@ -269,30 +261,22 @@ export default class MsgLocalizationForm extends React.Component<
269261
'Sending messages over a WhatsApp channel requires that a template be used if you have not received a message from a contact in the last 24 hours. Setting a template to use over WhatsApp is especially important for the first message in your flow.'
270262
)}
271263
</p>
272-
{this.state.templating && this.state.templating.variables.length > 0 ? (
273-
<>
274-
{range(0, this.state.templating.variables.length).map((num: number) => {
275-
const entry = this.state.templateVariables[num] || { value: '' };
276-
return (
277-
<div className={styles.variable} key={'tr_arg_' + num}>
278-
<TextInputElement
279-
name={`${i18n.t('forms.variable', 'Variable')} ${num + 1}`}
280-
showLabel={false}
281-
placeholder={`${this.props.language.name} ${variable} ${num + 1}`}
282-
onChange={(updatedText: string) => {
283-
this.handleTemplateVariableChanged(updatedText, num);
284-
}}
285-
entry={entry}
286-
autocomplete={true}
287-
/>
288-
</div>
289-
);
290-
})}
291-
</>
264+
{this.state.templating ? (
265+
<TembaComponent
266+
tag="temba-template-editor"
267+
eventHandlers={{
268+
'temba-content-changed': this.handleTemplateVariableChanged
269+
}}
270+
template={this.state.templating.template.uuid}
271+
url={this.props.assetStore.templates.endpoint}
272+
lang={this.props.language.id}
273+
params={JSON.stringify(this.state.params)}
274+
translating={true}
275+
></TembaComponent>
292276
) : null}
293277
</>
294278
),
295-
checked: hasLocalizedValue
279+
checked: true //hasLocalizedValue
296280
});
297281
}
298282

src/components/flow/actions/localization/__snapshots__/MsgLocalizationForm.test.ts.snap

+2-2
Original file line numberDiff line numberDiff line change
@@ -281,11 +281,11 @@ Object {
281281
"message": Object {
282282
"value": "",
283283
},
284+
"params": Object {},
284285
"quickReplies": Object {
285286
"validationFailures": Array [],
286287
"value": Array [],
287288
},
288-
"templateVariables": Array [],
289289
"templating": null,
290290
"uploadError": "",
291291
"uploadInProgress": false,
@@ -317,6 +317,7 @@ Object {
317317
"validationFailures": Array [],
318318
"value": "What is your favorite color?",
319319
},
320+
"params": Object {},
320321
"quickReplies": Object {
321322
"validationFailures": Array [],
322323
"value": Array [
@@ -325,7 +326,6 @@ Object {
325326
"blue",
326327
],
327328
},
328-
"templateVariables": Array [],
329329
"templating": null,
330330
"uploadError": "",
331331
"uploadInProgress": false,

src/components/flow/actions/localization/helpers.ts

+4-16
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { MsgLocalizationFormState } from 'components/flow/actions/localization/M
33
import { Types } from 'config/interfaces';
44
import { getTypeConfig } from 'config/typeConfigs';
55
import { NodeEditorSettings, StringEntry } from 'store/nodeEditor';
6-
import { SendMsg, MsgTemplating, SayMsg } from 'flowTypes';
6+
import { SendMsg, SayMsg } from 'flowTypes';
77
import { Attachment } from '../sendmsg/attachments';
88

99
export const initializeLocalizedKeyForm = (
@@ -30,7 +30,7 @@ export const initializeLocalizedForm = (settings: NodeEditorSettings): MsgLocali
3030
const state: MsgLocalizationFormState = {
3131
message: { value: '' },
3232
quickReplies: { value: [] },
33-
templateVariables: [],
33+
params: {},
3434
templating: null,
3535
audio: { value: null },
3636
valid: true,
@@ -49,17 +49,11 @@ export const initializeLocalizedForm = (settings: NodeEditorSettings): MsgLocali
4949
) {
5050
if (settings.originalAction && (settings.originalAction as any).templating) {
5151
state.templating = (settings.originalAction as any).templating;
52-
state.templateVariables = state.templating.variables.map((value: string) => {
53-
return {
54-
value: ''
55-
};
56-
});
5752
}
5853

5954
for (const localized of settings.localizations) {
6055
if (localized.isLocalized()) {
6156
const localizedObject = localized.getObject() as any;
62-
6357
if (localizedObject.text) {
6458
const action = localizedObject as (SendMsg & SayMsg);
6559
state.message.value = 'text' in localized.localizedKeys ? action.text : '';
@@ -88,14 +82,8 @@ export const initializeLocalizedForm = (settings: NodeEditorSettings): MsgLocali
8882
state.valid = true;
8983
}
9084

91-
if (localizedObject.variables) {
92-
const templating = localizedObject as MsgTemplating;
93-
state.templateVariables = templating.variables.map((value: string) => {
94-
return {
95-
value: 'variables' in localized.localizedKeys ? value : ''
96-
};
97-
});
98-
state.valid = true;
85+
if (localized.localizedKeys.params) {
86+
state.params[localizedObject.name] = localizedObject.params;
9987
}
10088
}
10189
}

src/components/flow/actions/saymsg/__snapshots__/SayMsgForm.test.ts.snap

-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ exports[`SayMsgForm render should render 1`] = `
2828
"localizeableKeys": Array [
2929
"text",
3030
"quick_replies",
31-
"templating.variables",
3231
],
3332
"massageForDisplay": [Function],
3433
"name": "Send Message",

0 commit comments

Comments
 (0)