diff --git a/public/index.html b/public/index.html
index 29505dc7e..74daf833a 100644
--- a/public/index.html
+++ b/public/index.html
@@ -350,7 +350,8 @@
endpoints: {
globals: base + 'globals',
groups: base + 'groups',
- fields: base + 'fields',
+ fields: base + 'fields?scope=contact',
+ waGroupFields: base + 'fields?scope=group',
labels: base + 'labels',
optins: base + 'optins',
channels: base + 'channels',
diff --git a/src/components/__snapshots__/index.test.ts.snap b/src/components/__snapshots__/index.test.ts.snap
index 32bc3fd98..343a53e45 100644
--- a/src/components/__snapshots__/index.test.ts.snap
+++ b/src/components/__snapshots__/index.test.ts.snap
@@ -30,6 +30,7 @@ Array [
"topics": "/assets/topics.json",
"users": "/assets/users.json",
"validateMedia": "",
+ "waGroupFields": "/assets/fields.json",
},
"a4f64f1b-85bc-477e-b706-de313a022979",
undefined,
diff --git a/src/components/flow/actions/action/Action.module.scss b/src/components/flow/actions/action/Action.module.scss
index 230b639f0..0c971c8ae 100644
--- a/src/components/flow/actions/action/Action.module.scss
+++ b/src/components/flow/actions/action/Action.module.scss
@@ -55,7 +55,8 @@
&.enter_flow,
&.link_google_sheet,
&.request_optin,
- &.enter_flow {
+ &.enter_flow,
+ &.set_wa_group_field {
width: $node_min_width - $action_padding * 2;
padding: $action_padding;
}
diff --git a/src/components/flow/actions/updategroup/UpdateGroup.tsx b/src/components/flow/actions/updategroup/UpdateGroup.tsx
new file mode 100644
index 000000000..cf78b2c60
--- /dev/null
+++ b/src/components/flow/actions/updategroup/UpdateGroup.tsx
@@ -0,0 +1,40 @@
+import { Types } from 'config/interfaces';
+import { SetContactAttribute } from 'flowTypes';
+import * as React from 'react';
+import { emphasize } from 'utils';
+import i18n from 'config/i18n';
+const styles = require('components/shared.module.scss');
+
+const withEmph = (text: string, emph: boolean) => (emph ? emphasize(text) : text);
+
+export const renderSetText = (
+ name: string,
+ value: string,
+ emphasizeName: boolean = false
+): JSX.Element => {
+ if (value) {
+ return (
+
+ Set {withEmph(name, emphasizeName)} to {emphasize(value)}.
+
+ );
+ } else {
+ return (
+
+ {i18n.t('forms.clear', 'Clear')} {withEmph(name, emphasizeName)}.
+
+ );
+ }
+};
+
+const UpdateGroupComp: React.SFC = (
+ action: SetContactAttribute
+): JSX.Element => {
+ if (action.type === Types.set_wa_group_field) {
+ return renderSetText(action.field.name, action.value, true);
+ }
+
+ return null;
+};
+
+export default UpdateGroupComp;
diff --git a/src/components/flow/actions/updategroup/UpdateGroupForm.module.scss b/src/components/flow/actions/updategroup/UpdateGroupForm.module.scss
new file mode 100644
index 000000000..446a7116e
--- /dev/null
+++ b/src/components/flow/actions/updategroup/UpdateGroupForm.module.scss
@@ -0,0 +1,3 @@
+.value {
+ margin-top: 14px;
+}
\ No newline at end of file
diff --git a/src/components/flow/actions/updategroup/UpdateGroupForm.tsx b/src/components/flow/actions/updategroup/UpdateGroupForm.tsx
new file mode 100644
index 000000000..8e4a4f28f
--- /dev/null
+++ b/src/components/flow/actions/updategroup/UpdateGroupForm.tsx
@@ -0,0 +1,166 @@
+import { react as bindCallbacks } from 'auto-bind';
+import Dialog, { ButtonSet } from 'components/dialog/Dialog';
+import { ActionFormProps } from 'components/flow/props';
+import AssetSelector from 'components/form/assetselector/AssetSelector';
+import TextInputElement from 'components/form/textinput/TextInputElement';
+import TypeList from 'components/nodeeditor/TypeList';
+import { fakePropType } from 'config/ConfigProvider';
+import { Types } from 'config/interfaces';
+import * as React from 'react';
+import { Asset, AssetType, updateAssets } from 'store/flowContext';
+import * as mutators from 'store/mutators';
+import { mergeForm } from 'store/nodeEditor';
+import { DispatchWithState, GetState } from 'store/thunks';
+
+import styles from './UpdateGroupForm.module.scss';
+import i18n from 'config/i18n';
+import { renderIssues } from '../helpers';
+import { SelectOption } from 'components/form/select/SelectElement';
+import { getName, initializeForm, stateToAction, UpdateGroupFormState } from './helpers';
+
+export default class UpdateGroupForm extends React.Component<
+ ActionFormProps,
+ UpdateGroupFormState
+> {
+ public static contextTypes = {
+ config: fakePropType
+ };
+
+ constructor(props: ActionFormProps) {
+ super(props);
+
+ this.state = initializeForm(this.props.nodeSettings);
+
+ bindCallbacks(this, {
+ include: [/^get/, /^on/, /^handle/]
+ });
+ }
+
+ private handleUpdate(
+ keys: {
+ type?: Types;
+ name?: string;
+ channel?: Asset;
+ language?: Asset;
+ status?: SelectOption;
+ field?: Asset;
+ settings?: SelectOption;
+ fieldValue?: string;
+ },
+ submitting = false
+ ): boolean {
+ const updates: Partial = {};
+
+ if (keys.hasOwnProperty('type')) {
+ updates.type = keys.type;
+ }
+
+ if (keys.hasOwnProperty('field')) {
+ updates.field = { value: keys.field };
+ }
+
+ if (keys.hasOwnProperty('fieldValue')) {
+ updates.fieldValue = { value: keys.fieldValue, validationFailures: [] };
+ }
+ const updated = mergeForm(this.state, updates);
+ this.setState(updated);
+ return updated.valid;
+ }
+
+ private handlePropertyChange(selected: any[]): boolean {
+ const selection = selected[0];
+ if (selection) {
+ return this.handleUpdate({
+ type: Types.set_wa_group_field,
+ field: selection
+ });
+ }
+ }
+
+ private handleFieldUpdate(fieldValue: string): boolean {
+ return this.handleUpdate({ fieldValue });
+ }
+
+ private onUpdated(dispatch: DispatchWithState, getState: GetState): void {
+ const {
+ flowContext: { assetStore }
+ } = getState();
+
+ if (this.state.field.value.type === AssetType.Field) {
+ dispatch(updateAssets(mutators.addAssets('fields', assetStore, [this.state.field.value])));
+ }
+ }
+
+ public handleFieldAdded(field: Asset): void {
+ // update our store with our new group
+ this.props.addAsset('fields', field);
+ this.handlePropertyChange([field]);
+ }
+
+ private handleSave(): void {
+ let valid = this.state.valid;
+
+ if (valid) {
+ // do the saving!
+ this.props.updateAction(stateToAction(this.props.nodeSettings, this.state), this.onUpdated);
+ this.props.onClose(true);
+ }
+ }
+
+ private getButtons(): ButtonSet {
+ return {
+ primary: { name: i18n.t('buttons.ok', 'Ok'), onClick: this.handleSave },
+ secondary: {
+ name: i18n.t('buttons.cancel', 'Cancel'),
+ onClick: () => this.props.onClose(true)
+ }
+ };
+ }
+
+ /**
+ * The value widget varies for the action type
+ */
+ private getValueWidget(): JSX.Element {
+ return (
+
+ );
+ }
+
+ public handleCreateAssetFromInput(input: string): any {
+ return { label: input, value_type: 'text' };
+ }
+
+ public render(): JSX.Element {
+ const typeConfig = this.props.typeConfig;
+ console.log();
+ return (
+
+ );
+ }
+}
diff --git a/src/components/flow/actions/updategroup/helpers.ts b/src/components/flow/actions/updategroup/helpers.ts
new file mode 100644
index 000000000..ba0689602
--- /dev/null
+++ b/src/components/flow/actions/updategroup/helpers.ts
@@ -0,0 +1,50 @@
+import { Types } from 'config/interfaces';
+import { getActionUUID } from '../helpers';
+import { FormEntry, FormState, NodeEditorSettings, StringEntry } from 'store/nodeEditor';
+import { SetWAGroupField } from 'flowTypes';
+
+export interface UpdateGroupFormState extends FormState {
+ type: Types;
+ field: FormEntry;
+ fieldValue: StringEntry;
+}
+
+export const initializeForm = (settings: NodeEditorSettings): UpdateGroupFormState => {
+ const state: UpdateGroupFormState = {
+ type: Types.set_wa_group_field,
+ valid: false,
+ field: { value: null },
+ fieldValue: { value: '' }
+ };
+
+ if (settings.originalAction) {
+ state.type = Types.set_wa_group_field;
+ const fieldAction = settings.originalAction as SetWAGroupField;
+ state.field = { value: { key: fieldAction.field.key, label: fieldAction.field.name } };
+ state.valid = true;
+ state.fieldValue = { value: fieldAction.value };
+ return state;
+ }
+
+ // default is updating name
+ return state;
+};
+
+export const stateToAction = (
+ settings: NodeEditorSettings,
+ state: UpdateGroupFormState
+): SetWAGroupField => {
+ /* istanbul ignore else */
+ const field = state.field.value;
+
+ return {
+ uuid: getActionUUID(settings, Types.set_wa_group_field),
+ type: Types.set_wa_group_field,
+ field,
+ value: state.fieldValue.value
+ };
+};
+
+export const getName = (asset: any): string => {
+ return asset.label || asset.name || asset.key;
+};
diff --git a/src/components/shared.module.scss b/src/components/shared.module.scss
index bd54ade99..72818b090 100644
--- a/src/components/shared.module.scss
+++ b/src/components/shared.module.scss
@@ -114,6 +114,10 @@
background: $teal;
}
+.set_wa_group_field {
+ background: $deep_amethyst;
+}
+
.play_audio {
background: $yellow;
}
@@ -122,11 +126,13 @@
$color_1: tomato;
$color_2: lighten(tomato, 3%);
- background-image: repeating-linear-gradient(120deg,
- $color_1,
- $color_1 6px,
- $color_2 6px,
- $color_2 18px) !important;
+ background-image: repeating-linear-gradient(
+ 120deg,
+ $color_1,
+ $color_1 6px,
+ $color_2 6px,
+ $color_2 18px
+ ) !important;
}
.missing_asset {
@@ -181,4 +187,4 @@
.alert {
background: $red;
-}
\ No newline at end of file
+}
diff --git a/src/config/__snapshots__/typeConfigs.test.ts.snap b/src/config/__snapshots__/typeConfigs.test.ts.snap
index fb563ef03..654afd445 100644
--- a/src/config/__snapshots__/typeConfigs.test.ts.snap
+++ b/src/config/__snapshots__/typeConfigs.test.ts.snap
@@ -478,6 +478,13 @@ Array [
"name": "Request Opt-In",
"type": "request_optin",
},
+ Object {
+ "component": [Function],
+ "description": "Update the group",
+ "form": [Function],
+ "name": "Update Group",
+ "type": "set_wa_group_field",
+ },
]
`;
@@ -793,6 +800,13 @@ Object {
"name": "Save Flow Result",
"type": "set_run_result",
},
+ "set_wa_group_field": Object {
+ "component": [Function],
+ "description": "Update the group",
+ "form": [Function],
+ "name": "Update Group",
+ "type": "set_wa_group_field",
+ },
"split_by_airtime": Object {
"aliases": Array [
"split_by_airtime",
diff --git a/src/config/i18n/defaults.json b/src/config/i18n/defaults.json
index bc6ace0fc..f733d0e79 100644
--- a/src/config/i18n/defaults.json
+++ b/src/config/i18n/defaults.json
@@ -137,6 +137,10 @@
"wait_for_video": {
"description": "Wait for a video",
"name": "Wait for Video"
+ },
+ "set_wa_group_field": {
+ "description": "Update the group",
+ "name": "Update Group"
}
},
"add": "Add",
@@ -207,6 +211,7 @@
"clear": "Clear",
"confidence": "confidence",
"contact_field": "Contact Field",
+ "wa_group_field": "WhatsApp Group Field",
"contact_query": "Contact Query",
"continue_when_no_response": "Continue when there is no response",
"create_group": "Create Group",
@@ -514,4 +519,4 @@
"header": "Flow Translation",
"label": "Translations"
}
-}
\ No newline at end of file
+}
diff --git a/src/config/interfaces.ts b/src/config/interfaces.ts
index 37fc9ad9c..3e86005c1 100644
--- a/src/config/interfaces.ts
+++ b/src/config/interfaces.ts
@@ -64,7 +64,8 @@ export enum Types {
request_optin = 'request_optin',
missing = 'missing',
say_msg = 'say_msg',
- play_audio = 'play_audio'
+ play_audio = 'play_audio',
+ set_wa_group_field = 'set_wa_group_field'
}
export enum Operators {
diff --git a/src/config/typeConfigs.ts b/src/config/typeConfigs.ts
index 4460afa15..fe1b6743b 100644
--- a/src/config/typeConfigs.ts
+++ b/src/config/typeConfigs.ts
@@ -75,6 +75,8 @@ import SheetForm from 'components/flow/routers/sheet/SheetForm';
import Sheet from 'components/flow/routers/sheet/Sheet';
import RequestOptInForm from 'components/flow/actions/requestoptin/RequestOptInForm';
import RequestOptInComp from 'components/flow/actions/requestoptin/RequestOptIn';
+import UpdateGroupForm from 'components/flow/actions/updategroup/UpdateGroupForm';
+import UpdateGroupComp from 'components/flow/actions/updategroup/UpdateGroup';
const dedupeTypeConfigs = (typeConfigs: Type[]) => {
const map: any = {};
@@ -576,6 +578,13 @@ export const typeConfigList: Type[] = [
form: RequestOptInForm,
component: RequestOptInComp,
filter: FeatureFilter.HAS_OPTINS
+ },
+ {
+ type: Types.set_wa_group_field,
+ name: i18n.t('actions.set_wa_group_field.name', 'Update Group'),
+ description: i18n.t('actions.set_wa_group_field.description', 'Update the group'),
+ form: UpdateGroupForm,
+ component: UpdateGroupComp
}
// {type: 'random', name: 'Random Split', description: 'Split them up randomly', form: RandomRouterForm}
];
diff --git a/src/external/index.ts b/src/external/index.ts
index 5926a65f6..8286e92fc 100644
--- a/src/external/index.ts
+++ b/src/external/index.ts
@@ -109,6 +109,7 @@ export const getCookie = (name: string): string => {
};
export const postNewAsset = (assets: Assets, payload: any): Promise => {
+ console.log('yes');
// if we have a csrf in our cookie, pass it along as a header
const csrf = getCookie('csrftoken');
const headers = csrf ? { 'X-CSRFToken': csrf } : {};
@@ -306,6 +307,12 @@ export const createAssetStore = (endpoints: Endpoints): Promise => {
id: 'key',
items: {}
},
+ waGroupFields: {
+ endpoint: getURL(endpoints.waGroupFields),
+ type: AssetType.Field,
+ id: 'key',
+ items: {}
+ },
globals: {
endpoint: getURL(endpoints.globals),
type: AssetType.Global,
diff --git a/src/flowTypes.ts b/src/flowTypes.ts
index af71adc61..00b55aea4 100644
--- a/src/flowTypes.ts
+++ b/src/flowTypes.ts
@@ -27,6 +27,7 @@ export interface Endpoints {
resthooks: string;
recents: string;
fields: string;
+ waGroupFields: string;
globals: string;
groups: string;
recipients: string;
@@ -413,6 +414,11 @@ export interface SendInteractiveMsg extends Action {
attachment_type?: string;
}
+export interface SetWAGroupField extends Action {
+ field: Field;
+ value: string;
+}
+
export interface Delay extends Action {
delay: string;
}
diff --git a/src/palette_ec.scss b/src/palette_ec.scss
index 8ee05f910..ade8141f1 100644
--- a/src/palette_ec.scss
+++ b/src/palette_ec.scss
@@ -17,3 +17,5 @@ $brown: #897644;
$dark_blue: #4d7dad;
$blue_gray: #758590;
+
+$deep_amethyst: #967aa1;
diff --git a/src/test/config.ts b/src/test/config.ts
index a013e3578..84a70e0b3 100644
--- a/src/test/config.ts
+++ b/src/test/config.ts
@@ -26,6 +26,7 @@ const config: FlowEditorConfig = {
globals: '/assets/globals.json',
groups: '/assets/groups.json',
fields: '/assets/fields.json',
+ waGroupFields: '/assets/fields.json',
recipients: '/assets/recipients.json',
contacts: '/assets/recipients.json',
labels: '/assets/labels.json',
diff --git a/src/test/utils.tsx b/src/test/utils.tsx
index a6ebae200..1de0c529e 100644
--- a/src/test/utils.tsx
+++ b/src/test/utils.tsx
@@ -34,6 +34,7 @@ export const TEST_DEFINITION: FlowDefinition = {
export const EMPTY_TEST_ASSETS = {
channels: { items: {}, type: AssetType.Channel },
fields: { items: {}, type: AssetType.Field },
+ waGroupFields: { items: {}, type: AssetType.Field },
languages: { items: {}, type: AssetType.Language },
labels: { items: {}, type: AssetType.Label },
results: { items: {}, type: AssetType.Result },
diff --git a/src/testUtils/assetCreators.ts b/src/testUtils/assetCreators.ts
index c9737307f..29b311dae 100644
--- a/src/testUtils/assetCreators.ts
+++ b/src/testUtils/assetCreators.ts
@@ -475,6 +475,7 @@ export const getActionFormProps = (action: AnyAction): ActionFormProps => ({
assetStore: {
channels: { items: {}, type: AssetType.Channel },
fields: { items: {}, type: AssetType.Field },
+ waGroupFields: { items: {}, type: AssetType.Field },
languages: { items: {}, type: AssetType.Language },
labels: { items: {}, type: AssetType.Label },
results: { items: {}, type: AssetType.Result },