Skip to content

Commit

Permalink
copy template over
Browse files Browse the repository at this point in the history
  • Loading branch information
ktalebian committed May 19, 2020
1 parent 5c0ab48 commit b768921
Show file tree
Hide file tree
Showing 17 changed files with 401 additions and 2 deletions.
52 changes: 50 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,50 @@
# flex-plugin-ts
A base TypeScript template for Flex Plugins
# Your custom Twilio Flex Plugin

Twilio Flex Plugins allow you to customize the appearance and behavior of [Twilio Flex](https://www.twilio.com/flex). If you want to learn more about the capabilities and how to use the API, check out our [Flex documentation](https://www.twilio.com/docs/flex).

## Setup

Make sure you have [Node.js](https://nodejs.org) as well as [`npm`](https://npmjs.com) installed.

Afterwards, install the dependencies by running `npm install`:

```bash
cd {{pluginFileName}}

# If you use npm
npm install
```

## Development

In order to develop locally, you can use the Webpack Dev Server by running:

```bash
npm start
```

This will automatically start up the Webpack Dev Server and open the browser for you. Your app will run on `http://localhost:3000`. If you want to change that you can do this by setting the `PORT` environment variable:

```bash
PORT=3001 npm start
```

When you make changes to your code, the browser window will be automatically refreshed.

## Deploy

When you are ready to deploy your plugin, in your terminal run:

```bash
npm run deploy
```

This will publish your plugin as a Private Asset that is accessible by the Functions & Assets API. If you want to deploy your plugin as a Public Asset, you may pass --public to your deploy command:

```bash
npm run deploy --public
```

For more details on deploying your plugin, refer to the [deploying your plugin guide](https://www.twilio.com/docs/flex/plugins#deploying-your-plugin).

Note: Common packages like `React`, `ReactDOM`, `Redux` and `ReactRedux` are not bundled with the build because they are treated as external dependencies so the plugin will depend on Flex to provide them globally.
12 changes: 12 additions & 0 deletions craco.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const config = require('craco-config-flex-plugin');

module.exports = {
...config,
plugins: [
// Customize app configuration (such as webpack, devServer, linter, etc) by creating a Craco plugin.
// See https://github.com/sharegate/craco/tree/master/packages/craco#develop-a-plugin for more detail.
//
// Please note that Craco plugins have nothing to do with Flex plugins; it's just a naming coincidence.
// Changes to this file are optional, you will not need to modify it for normal Flex Plugin development.
]
};
56 changes: 56 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"name": "{{name}}",
"version": "0.0.0",
"private": true,
"scripts": {
"bootstrap": "flex-plugin check-start",
"prebuild": "rimraf build && npm run bootstrap",
"build": "flex-plugin build",
"clear": "flex-plugin clear",
"predeploy": "npm run build",
"deploy": "flex-plugin deploy",
"eject": "flex-plugin eject",
"info": "flex-plugin info",
"postinstall": "npm run bootstrap",
"list": "flex-plugin list",
"remove": "flex-plugin remove",
"prestart": "npm run bootstrap",
"start": "flex-plugin start",
"test": "flex-plugin test --env=jsdom"
},
"dependencies": {
"craco-config-flex-plugin": "^3",
"flex-plugin": "^3",
"flex-plugin-scripts": "^3",
"react": "16.5.2",
"react-dom": "16.5.2",
"react-emotion": "9.2.6",
"react-scripts": "3.4.1",
"typescript": "^3.6.4"
},
"devDependencies": {
"@twilio/flex-ui": "^1",
"@types/enzyme": "^3.10.3",
"@types/jest": "^24.0.18",
"@types/node": "^12.7.12",
"@types/react": "^16.8.16",
"@types/react-dom": "^16.8.4",
"@types/react-redux": "^7.1.1",
"babel-polyfill": "^6.26.0",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0",
"rimraf": "^3.0.0"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
20 changes: 20 additions & 0 deletions public/appConfig.example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// your account sid
var accountSid = 'accountSid';

// set to /plugins.json for local dev
// set to /plugins.local.build.json for testing your build
// set to "" for the default live plugin loader
var pluginServiceUrl = '/plugins.json';

var appConfig = {
pluginService: {
enabled: true,
url: pluginServiceUrl,
},
sso: {
accountSid: accountSid
},
ytica: false,
logLevel: 'debug',
showSupervisorDesktopView: true,
};
18 changes: 18 additions & 0 deletions public/appConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// your account sid
var accountSid = '{{accountSid}}';

// set to /plugins.json for local dev
// set to /plugins.local.build.json for testing your build
// set to "" for the default live plugin loader
var pluginServiceUrl = '/plugins.json';

var appConfig = {
pluginService: {
enabled: true,
url: pluginServiceUrl,
},
sso: {
accountSid: accountSid
},
logLevel: 'debug',
};
1 change: 1 addition & 0 deletions public/plugins.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{pluginJsonContent}}
13 changes: 13 additions & 0 deletions public/plugins.local.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[
{
"name": "{{pluginClassName}}",
"version": "0.0.0",
"class": "{{pluginClassName}}",
"requires": [
{
"@twilio/flex-ui": "{{flexSdkVersion}}"
}
],
"src": "http://127.0.0.1:8085"
}
]
46 changes: 46 additions & 0 deletions src/DemoPlugin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import * as Flex from '@twilio/flex-ui';
import { FlexPlugin } from 'flex-plugin';

import CustomTaskListContainer from './components/CustomTaskList/CustomTaskList.Container';
import reducers, { namespace } from './states';

const PLUGIN_NAME = '{{pluginClassName}}';

export default class {{pluginClassName}} extends FlexPlugin {
constructor() {
super(PLUGIN_NAME);
}

/**
* This code is run when your plugin is being started
* Use this to modify any UI components or attach to the actions framework
*
* @param flex { typeof Flex }
* @param manager { Flex.Manager }
*/
init(flex: typeof Flex, manager: Flex.Manager) {
this.registerReducers(manager);

const options: Flex.ContentFragmentProps = { sortOrder: -1 };
flex.AgentDesktopView
.Panel1
.Content
.add(<CustomTaskListContainer key="demo-component" />, options);
}

/**
* Registers the plugin reducers
*
* @param manager { Flex.Manager }
*/
private registerReducers(manager: Flex.Manager) {
if (!manager.store.addReducer) {
// tslint: disable-next-line
console.error(`You need FlexUI > 1.9.0 to use built-in redux; you are currently on ${Flex.VERSION}`);
return;
}

manager.store.addReducer(namespace, reducers);
}
}
24 changes: 24 additions & 0 deletions src/components/CustomTaskList/CustomTaskList.Container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { AppState } from '../../states';
import { connect } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';

import { Actions } from '../../states/CustomTaskListState';
import CustomTaskList from './CustomTaskList';

export interface StateToProps {
isOpen: boolean;
}

export interface DispatchToProps {
dismissBar: () => void;
}

const mapStateToProps = (state: AppState): StateToProps => ({
isOpen: state['{{pluginNamespace}}'].customTaskList.isOpen,
});

const mapDispatchToProps = (dispatch: Dispatch<any>): DispatchToProps => ({
dismissBar: bindActionCreators(Actions.dismissBar, dispatch),
});

export default connect(mapStateToProps, mapDispatchToProps)(CustomTaskList);
14 changes: 14 additions & 0 deletions src/components/CustomTaskList/CustomTaskList.Styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { default as styled } from 'react-emotion';

export const CustomTaskListComponentStyles = styled('div')`
padding: 10px;
margin: 0px;
color: #fff;
background: #000;
.accented {
color: red;
cursor: pointer;
float: right;
}
`;
29 changes: 29 additions & 0 deletions src/components/CustomTaskList/CustomTaskList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';

import { CustomTaskListComponentStyles } from './CustomTaskList.Styles';
import { StateToProps, DispatchToProps } from './CustomTaskList.Container';

interface OwnProps {
// Props passed directly to the component
}

// Props should be a combination of StateToProps, DispatchToProps, and OwnProps
type Props = StateToProps & DispatchToProps & OwnProps;

// It is recommended to keep components stateless and use redux for managing states
const CustomTaskList = (props: Props) => {
if (!props.isOpen) {
return null;
}

return (
<CustomTaskListComponentStyles>
This is a dismissible demo component
<i className="accented" onClick={props.dismissBar}>
close
</i>
</CustomTaskListComponentStyles>
);
};

export default CustomTaskList;
15 changes: 15 additions & 0 deletions src/components/__tests__/CustomTaskListComponent.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { shallow } from 'enzyme';

import CustomTaskList from '../CustomTaskList/CustomTaskList';

describe('CustomTaskListComponent', () => {
it('should render demo component', () => {
const props = {
isOpen: true,
dismissBar: () => undefined,
};
const wrapper = shallow(<CustomTaskList {...props}/>);
expect(wrapper.render().text()).toMatch('This is a dismissible demo component');
});
});
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import * as FlexPlugin from 'flex-plugin';
import {{pluginClassName}} from './{{pluginClassName}}';

FlexPlugin.loadPlugin({{pluginClassName}});
8 changes: 8 additions & 0 deletions src/setupTests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
require('babel-polyfill');

import { configure } from 'enzyme/build';
import Adapter from 'enzyme-adapter-react-16/build';

configure({
adapter: new Adapter(),
});
29 changes: 29 additions & 0 deletions src/states/CustomTaskListState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Action } from './index';

const ACTION_DISMISS_BAR = 'DISMISS_BAR';

export interface CustomTaskListState {
isOpen: boolean;
}

const initialState: CustomTaskListState = {
isOpen: true,
};

export class Actions {
public static dismissBar = (): Action => ({ type: ACTION_DISMISS_BAR });
}

export function reduce(state: CustomTaskListState = initialState, action: Action) {
switch (action.type) {
case ACTION_DISMISS_BAR: {
return {
...state,
isOpen: false,
};
}

default:
return state;
}
}
26 changes: 26 additions & 0 deletions src/states/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { AppState as FlexAppState } from '@twilio/flex-ui';
import { combineReducers, Action as ReduxAction } from 'redux';

import { CustomTaskListState, reduce as CustomTaskListReducer } from './CustomTaskListState';

// Register your redux store under a unique namespace
export const namespace = '{{pluginNamespace}}';

// Extend this payload to be of type that your ReduxAction is
export interface Action extends ReduxAction {
payload?: any;
}

// Register all component states under the namespace
export interface AppState {
flex: FlexAppState;
'{{pluginNamespace}}': {
customTaskList: CustomTaskListState;
// Other states
};
}

// Combine the reducers
export default combineReducers({
customTaskList: CustomTaskListReducer
});
Loading

0 comments on commit b768921

Please sign in to comment.