diff --git a/README.md b/README.md
index 325a802..a2eaea7 100644
--- a/README.md
+++ b/README.md
@@ -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.
\ No newline at end of file
diff --git a/craco.config.js b/craco.config.js
new file mode 100644
index 0000000..aea5856
--- /dev/null
+++ b/craco.config.js
@@ -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.
+ ]
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..6dd96d2
--- /dev/null
+++ b/package.json
@@ -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"
+ ]
+ }
+}
diff --git a/public/appConfig.example.js b/public/appConfig.example.js
new file mode 100644
index 0000000..e497f9e
--- /dev/null
+++ b/public/appConfig.example.js
@@ -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,
+};
diff --git a/public/appConfig.js b/public/appConfig.js
new file mode 100644
index 0000000..e2e5ed3
--- /dev/null
+++ b/public/appConfig.js
@@ -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',
+};
diff --git a/public/plugins.json b/public/plugins.json
new file mode 100644
index 0000000..fa8b43b
--- /dev/null
+++ b/public/plugins.json
@@ -0,0 +1 @@
+{{pluginJsonContent}}
diff --git a/public/plugins.local.build.json b/public/plugins.local.build.json
new file mode 100644
index 0000000..d1d5349
--- /dev/null
+++ b/public/plugins.local.build.json
@@ -0,0 +1,13 @@
+[
+ {
+ "name": "{{pluginClassName}}",
+ "version": "0.0.0",
+ "class": "{{pluginClassName}}",
+ "requires": [
+ {
+ "@twilio/flex-ui": "{{flexSdkVersion}}"
+ }
+ ],
+ "src": "http://127.0.0.1:8085"
+ }
+]
diff --git a/src/DemoPlugin.tsx b/src/DemoPlugin.tsx
new file mode 100644
index 0000000..32e675d
--- /dev/null
+++ b/src/DemoPlugin.tsx
@@ -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(, 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);
+ }
+}
diff --git a/src/components/CustomTaskList/CustomTaskList.Container.ts b/src/components/CustomTaskList/CustomTaskList.Container.ts
new file mode 100644
index 0000000..3258752
--- /dev/null
+++ b/src/components/CustomTaskList/CustomTaskList.Container.ts
@@ -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): DispatchToProps => ({
+ dismissBar: bindActionCreators(Actions.dismissBar, dispatch),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(CustomTaskList);
diff --git a/src/components/CustomTaskList/CustomTaskList.Styles.ts b/src/components/CustomTaskList/CustomTaskList.Styles.ts
new file mode 100644
index 0000000..ec1077f
--- /dev/null
+++ b/src/components/CustomTaskList/CustomTaskList.Styles.ts
@@ -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;
+ }
+`;
diff --git a/src/components/CustomTaskList/CustomTaskList.tsx b/src/components/CustomTaskList/CustomTaskList.tsx
new file mode 100644
index 0000000..6f7dcae
--- /dev/null
+++ b/src/components/CustomTaskList/CustomTaskList.tsx
@@ -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 (
+
+ This is a dismissible demo component
+
+ close
+
+
+ );
+};
+
+export default CustomTaskList;
diff --git a/src/components/__tests__/CustomTaskListComponent.spec.tsx b/src/components/__tests__/CustomTaskListComponent.spec.tsx
new file mode 100644
index 0000000..5c7e02c
--- /dev/null
+++ b/src/components/__tests__/CustomTaskListComponent.spec.tsx
@@ -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();
+ expect(wrapper.render().text()).toMatch('This is a dismissible demo component');
+ });
+});
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..3ccdba7
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,4 @@
+import * as FlexPlugin from 'flex-plugin';
+import {{pluginClassName}} from './{{pluginClassName}}';
+
+FlexPlugin.loadPlugin({{pluginClassName}});
diff --git a/src/setupTests.js b/src/setupTests.js
new file mode 100644
index 0000000..9554ff9
--- /dev/null
+++ b/src/setupTests.js
@@ -0,0 +1,8 @@
+require('babel-polyfill');
+
+import { configure } from 'enzyme/build';
+import Adapter from 'enzyme-adapter-react-16/build';
+
+configure({
+ adapter: new Adapter(),
+});
diff --git a/src/states/CustomTaskListState.ts b/src/states/CustomTaskListState.ts
new file mode 100644
index 0000000..1524caa
--- /dev/null
+++ b/src/states/CustomTaskListState.ts
@@ -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;
+ }
+}
diff --git a/src/states/index.ts b/src/states/index.ts
new file mode 100644
index 0000000..75a19a6
--- /dev/null
+++ b/src/states/index.ts
@@ -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
+});
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..9f3b58a
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,36 @@
+{
+ "compilerOptions": {
+ "baseUrl": "src",
+ "rootDir": ".",
+ "outDir": "build",
+ "target": "es5",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "preserve",
+ "noUnusedLocals": false,
+ "noUnusedParameters": false
+ },
+ "include": [
+ "./src/**/*"
+ ],
+ "exclude": [
+ "./**/*.test.ts",
+ "./**/*.test.tsx",
+ "./**/__mocks__/*.ts",
+ "./**/__mocks__/*.tsx"
+ ]
+}