Skip to content

Commit

Permalink
Merge pull request #1200 from nyaruka/feature/request-option
Browse files Browse the repository at this point in the history
Add request optin action
  • Loading branch information
ericnewcomer authored Sep 26, 2023
2 parents c8dd591 + 26d0c49 commit 38f20a3
Show file tree
Hide file tree
Showing 16 changed files with 252 additions and 14 deletions.
19 changes: 19 additions & 0 deletions lambda/optins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const optins = {
next: null,
previous: null,
results: [
{
uuid: '8fc583a0-0700-434d-b238-8053af1d040e',
name: 'Newsletter',
created_on: '2023-09-25T04:43:19.103443Z'
},
{
uuid: '806f52e7-ad6a-4ede-8793-6f51b60a30ec',
name: 'U-Report Polls',
created_on: '2023-09-23T00:18:41.572795Z'
}
]
};
const { getOpts } = require('./utils');

exports.handler = (evt, ctx, cb) => cb(null, getOpts({ body: JSON.stringify(optins) }));
1 change: 1 addition & 0 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@
groups: base + 'groups',
fields: base + 'fields',
labels: base + 'labels',
optins: base + 'optins',
channels: base + 'channels',
classifiers: base + 'classifiers',
ticketers: base + 'ticketers',
Expand Down
1 change: 1 addition & 0 deletions src/components/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Array [
"groups": "/assets/groups.json",
"labels": "/assets/labels.json",
"languages": "/assets/languages.json",
"optins": "/assets/optins.json",
"recents": "/assets/recents.json",
"recipients": "/assets/recipients.json",
"resthooks": "/assets/resthooks.json",
Expand Down
5 changes: 4 additions & 1 deletion src/components/flow/actions/action/Action.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
border-radius: 0px;
}
}

clear: both;
}

Expand Down Expand Up @@ -47,6 +48,7 @@
&.transfer_airtime,
&.open_ticket,
&.missing,
&.request_optin,
&.enter_flow {
width: $node_min_width - $action_padding * 2;
padding: $action_padding;
Expand All @@ -73,6 +75,7 @@
.overlay {
display: block;
}

.body {
background: #fff !important;
}
Expand Down Expand Up @@ -126,4 +129,4 @@
background: rgba(255, 255, 255, 0.9);
$color_1: rgba(0, 0, 0, 0.12);
$color_2: rgba(0, 0, 0, 0.1);
}
}
8 changes: 8 additions & 0 deletions src/components/flow/actions/requestoptin/RequestOptIn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { RequestOptIn } from 'flowTypes';
import * as React from 'react';

const RequestOptInComp: React.SFC<RequestOptIn> = (action: RequestOptIn): JSX.Element => {
return <div className="optin">{action.optin.name}</div>;
};

export default RequestOptInComp;
107 changes: 107 additions & 0 deletions src/components/flow/actions/requestoptin/RequestOptInForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
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 TypeList from 'components/nodeeditor/TypeList';
import { fakePropType } from 'config/ConfigProvider';
import * as React from 'react';
import { Asset } from 'store/flowContext';
import { FormState, mergeForm } from 'store/nodeEditor';
import { shouldRequireIf, validate } from 'store/validators';

import i18n from 'config/i18n';
import { Trans } from 'react-i18next';
import { renderIssues } from '../helpers';
import { initializeForm, stateToAction } from './helpers';

export interface RequestOptInFormState extends FormState {
optin: any;
}

export const controlLabelSpecId = 'label';

export default class AddLabelsForm extends React.PureComponent<
ActionFormProps,
RequestOptInFormState
> {
public static contextTypes = {
assetService: fakePropType
};

constructor(props: ActionFormProps) {
super(props);

this.state = initializeForm(this.props.nodeSettings);
bindCallbacks(this, {
include: [/^on/, /^handle/]
});
}

public handleSave(): void {
const valid = this.handleOptInChanged(this.state.optin.value!, true);

if (valid) {
const newAction = stateToAction(this.props.nodeSettings, this.state);
this.props.updateAction(newAction);
this.props.onClose(false);
}
}

public handleOptInChanged(selected: Asset[], submitting: boolean = false): boolean {
const updates: Partial<RequestOptInFormState> = {
optin: validate(i18n.t('forms.title', 'Name'), selected, [shouldRequireIf(submitting)])
};

const updated = mergeForm(this.state, updates);
this.setState(updated);
return updated.valid;
}

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)
}
};
}

public handleCreateAssetFromInput(input: string): any {
return { name: input };
}

public handleOptInCreated(optin: Asset): void {
console.log(optin);
this.handleOptInChanged([optin]);
}

public render(): JSX.Element {
const typeConfig = this.props.typeConfig;
return (
<Dialog title={typeConfig.name} headerClass={typeConfig.type} buttons={this.getButtons()}>
<TypeList __className="" initialType={typeConfig} onChange={this.props.onTypeChange} />
<p data-spec={controlLabelSpecId}>
<Trans i18nKey="forms.request_optin_summary">Select the Opt-In to request</Trans>
</p>

<AssetSelector
name={i18n.t('forms.request_optin.title', 'Request Opt-In')}
placeholder={i18n.t(
'enter_to_create_optin',
'Enter the name of an existing Opt-In or create a new one'
)}
assets={this.props.assetStore.optins}
entry={this.state.optin}
searchable={true}
expressions={true}
onChange={this.handleOptInChanged}
createPrefix={i18n.t('create_optin', 'Create Opt-In') + ': '}
createAssetFromInput={this.handleCreateAssetFromInput}
onAssetCreated={this.handleOptInCreated}
/>
{renderIssues(this.props)}
</Dialog>
);
}
}
32 changes: 32 additions & 0 deletions src/components/flow/actions/requestoptin/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { getActionUUID } from 'components/flow/actions/helpers';
import { Types } from 'config/interfaces';
import { RequestOptIn } from 'flowTypes';
import { NodeEditorSettings } from 'store/nodeEditor';
import { RequestOptInFormState } from './RequestOptInForm';

export const initializeForm = (settings: NodeEditorSettings): RequestOptInFormState => {
if (settings.originalAction && settings.originalAction.type === Types.request_optin) {
const action = settings.originalAction as RequestOptIn;
return {
optin: { value: action.optin },
valid: true
};
}

return {
optin: { value: null },
valid: false
};
};

export const stateToAction = (
settings: NodeEditorSettings,
formState: RequestOptInFormState
): RequestOptIn => {
const result = {
type: Types.request_optin,
optin: formState.optin.value[0],
uuid: getActionUUID(settings, Types.add_input_labels)
};
return result;
};
20 changes: 11 additions & 9 deletions src/components/shared.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
border-top-right-radius: 3px;
padding: 10px;
color: $red;

.icon {
display: inline-block;
vertical-align: middle;
Expand All @@ -38,6 +39,7 @@

.issue_help {
cursor: pointer;

&:hover {
color: lighten($red, 3%);
text-decoration: underline;
Expand Down Expand Up @@ -70,7 +72,8 @@

.msg,
.say_msg,
.send_msg {
.send_msg,
.request_optin {
background: $blue;
}

Expand Down Expand Up @@ -103,13 +106,11 @@
$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 {
Expand Down Expand Up @@ -144,6 +145,7 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;

.node_group {
padding-right: 5px;
}
Expand All @@ -159,4 +161,4 @@

.alert {
background: $red;
}
}
16 changes: 16 additions & 0 deletions src/config/__snapshots__/typeConfigs.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,14 @@ Array [
"name": "Split by URN Type",
"type": "split_by_scheme",
},
Object {
"component": [Function],
"description": "Send an Opt-In request",
"filter": "optins",
"form": [Function],
"name": "Request Opt-In",
"type": "request_optin",
},
]
`;

Expand Down Expand Up @@ -583,6 +591,14 @@ Object {
"name": "Remove from Group",
"type": "remove_contact_groups",
},
"request_optin": Object {
"component": [Function],
"description": "Send an Opt-In request",
"filter": "optins",
"form": [Function],
"name": "Request Opt-In",
"type": "request_optin",
},
"say_msg": Object {
"component": [Function],
"description": "Play a message",
Expand Down
4 changes: 3 additions & 1 deletion src/config/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export enum Types {
wait_for_video = 'wait_for_video',
wait_for_location = 'wait_for_location',
wait_for_image = 'wait_for_image',
request_optin = 'request_optin',
missing = 'missing',
say_msg = 'say_msg',
play_audio = 'play_audio'
Expand Down Expand Up @@ -103,7 +104,8 @@ export enum FeatureFilter {
HAS_CLASSIFIER = 'classifier',
HAS_TICKETER = 'ticketer',
HAS_FACEBOOK = 'facebook',
HAS_LOCATIONS = 'locations'
HAS_LOCATIONS = 'locations',
HAS_OPTINS = 'optins'
}

export interface FlowTypeVisibility {
Expand Down
16 changes: 13 additions & 3 deletions src/config/typeConfigs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ import i18n from 'config/i18n';
import SchemeRouterForm from 'components/flow/routers/scheme/SchemeRouterForm';
import TicketRouterForm from 'components/flow/routers/ticket/TicketRouterForm';
import OpenTicketComp from 'components/flow/actions/openticket/OpenTicket';
import RequestOptInForm from 'components/flow/actions/requestoptin/RequestOptInForm';
import RequestOptInComp from 'components/flow/actions/requestoptin/RequestOptIn';

const dedupeTypeConfigs = (typeConfigs: Type[]) => {
const map: any = {};
Expand Down Expand Up @@ -164,12 +166,12 @@ export const SCHEMES: Scheme[] = [
{
scheme: 'vk',
name: i18n.t('schemes.vk.name', 'VK'),
path: i18n.t('schemes.vk.path', 'VK ID'),
path: i18n.t('schemes.vk.path', 'VK ID')
},
{
scheme: 'discord',
name: i18n.t('schemes.discord.name', 'Discord'),
path: i18n.t('schemes.discord.path', 'Discord ID'),
path: i18n.t('schemes.discord.path', 'Discord ID')
},
{
scheme: 'webchat',
Expand All @@ -180,7 +182,7 @@ export const SCHEMES: Scheme[] = [
{
scheme: 'rocketchat',
name: i18n.t('schemes.rocketchat.name', 'RocketChat'),
path: i18n.t('schemes.rocketchat.path', 'RocketChat ID'),
path: i18n.t('schemes.rocketchat.path', 'RocketChat ID')
},
{
scheme: 'ext',
Expand Down Expand Up @@ -514,6 +516,14 @@ export const typeConfigList: Type[] = [
localization: RouterLocalizationForm,
localizeableKeys: ['exits'],
form: SchemeRouterForm
},
{
type: Types.request_optin,
name: i18n.t('actions.request_optin.name', 'Request Opt-In'),
description: i18n.t('actions.request_optin.description', 'Send an Opt-In request'),
form: RequestOptInForm,
component: RequestOptInComp,
filter: FeatureFilter.HAS_OPTINS
}
// {type: 'random', name: 'Random Split', description: 'Split them up randomly', form: RandomRouterForm}
];
Expand Down
5 changes: 5 additions & 0 deletions src/external/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,11 @@ export const createAssetStore = (endpoints: Endpoints): Promise<AssetStore> => {
type: AssetType.Label,
items: {}
},
optins: {
endpoint: getURL(endpoints.optins),
type: AssetType.OptIn,
items: {}
},
results: {
type: AssetType.Result,
items: {}
Expand Down
Loading

0 comments on commit 38f20a3

Please sign in to comment.