@imposium-hub/components 1.42.4 → 1.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Entry.ts CHANGED
@@ -101,6 +101,8 @@ import assetTags from './redux/reducers/asset-tags';
101
101
  import assetUploads from './redux/reducers/asset-uploads';
102
102
  import selectedAssets from './redux/reducers/selected-assets';
103
103
  import assetActions from './redux/actions/asset-list';
104
+ import activeBatch from './redux/reducers/active-batch';
105
+ import batchActions from './redux/actions/active-batch';
104
106
  /*
105
107
  Statics / Services
106
108
  */
@@ -222,6 +224,9 @@ export {
222
224
  addAssetTagToList,
223
225
  assetTags,
224
226
  assetActions,
227
+
228
+ activeBatch,
229
+ batchActions,
225
230
 
226
231
  AuthService,
227
232
  IIdentity,
@@ -109,7 +109,6 @@ let doubleClickTimeout : number;
109
109
  const DataTable : React.FC<IDataTableProps> = (props : IDataTableProps) => {
110
110
  const wrapperRef = React.useRef(null);
111
111
  const activeRowRef = React.useRef(null);
112
-
113
112
  const [didMount, setDidMount] = React.useState(false);
114
113
  const [data, setData] = React.useState([]);
115
114
  const [columns, setColumns] = React.useState([]);
@@ -60,6 +60,9 @@ interface IPublishWizardState {
60
60
  accessKey : string;
61
61
  credentials : ICredential[];
62
62
  done : boolean;
63
+ error : boolean;
64
+ next : boolean;
65
+ nextStep : boolean;
63
66
  }
64
67
 
65
68
  interface IBigButtonProps {
@@ -78,6 +81,7 @@ interface IBigLinkProps {
78
81
  }
79
82
 
80
83
  class PublishWizard extends React.PureComponent<IPublishWizardProps, IPublishWizardState> {
84
+ public emailWorkflow : any;
81
85
  private readonly hiddenFileInputRef : any;
82
86
  constructor(props) {
83
87
  super(props);
@@ -92,7 +96,11 @@ class PublishWizard extends React.PureComponent<IPublishWizardProps, IPublishWiz
92
96
  accessKey: null,
93
97
  credentials: [],
94
98
  done: false,
99
+ error: false,
100
+ next: false,
101
+ nextStep: false,
95
102
  };
103
+ this.emailWorkflow = React.createRef();
96
104
  this.hiddenFileInputRef = React.createRef();
97
105
  }
98
106
 
@@ -216,8 +224,8 @@ class PublishWizard extends React.PureComponent<IPublishWizardProps, IPublishWiz
216
224
 
217
225
  private renderLowerButtons() {
218
226
 
219
- const {screenIndex, done} = this.state;
220
- const {publishing} = this.props;
227
+ const { screenIndex, done, error, next } = this.state;
228
+ const { publishing } = this.props;
221
229
 
222
230
  const lowerButtons = [];
223
231
 
@@ -246,26 +254,47 @@ class PublishWizard extends React.PureComponent<IPublishWizardProps, IPublishWiz
246
254
  </Button>);
247
255
  } else {
248
256
  // display button when emailworkflow downloading state is true to close the modal
249
- if (done) {
257
+ if (done || error) {
250
258
  lowerButtons.push(<Button
251
- tooltip = {copy.publish.btnFinished}
259
+ tooltip = { error ? copy.publish.btnCancel : copy.publish.btnFinished }
252
260
  size={'large'}
253
261
  key = 'btn-finish'
254
262
  onClick={() => this.props.onClose()}
255
263
  color = 'primary'>
256
- {copy.publish.btnFinished}
264
+ { error ? copy.publish.btnCancel : copy.publish.btnFinished }
257
265
  </Button>);
258
266
  }
267
+
259
268
  const backIndex = (screenIndex === 1) ? 0 : 1;
269
+
260
270
  // back button
261
271
  lowerButtons.push(<Button
262
272
  tooltip = {copy.publish.btnBack}
263
273
  size={'large'}
264
274
  key = 'btn-back'
265
- onClick={() => this.setState({screenIndex: backIndex})}
275
+ onClick={() => this.setState({
276
+ screenIndex: backIndex,
277
+ next: false,
278
+ nextStep: false,
279
+ error: false,
280
+ done: false,
281
+ })}
266
282
  color = 'primary'>
267
283
  {copy.publish.btnBack}
268
284
  </Button>);
285
+
286
+ // reimport after column align
287
+ if ( error ) {
288
+ lowerButtons.push(<Button
289
+ tooltip = {copy.publish.btnNext}
290
+ size={'large'}
291
+ key = 'btn-next'
292
+ color = 'primary'
293
+ disabled = {!next}
294
+ onClick={() => this.nextClickHandler()}>
295
+ {copy.publish.btnNext}
296
+ </Button>);
297
+ }
269
298
  }
270
299
 
271
300
  return lowerButtons;
@@ -362,13 +391,30 @@ class PublishWizard extends React.PureComponent<IPublishWizardProps, IPublishWiz
362
391
  );
363
392
  }
364
393
 
394
+ private nextClickHandler = () => {
395
+ this.setState({nextStep: true});
396
+
397
+ if (this.state.screenIndex === 7) {
398
+ this.emailWorkflow.current.getWrappedInstance().proceedWithExport();
399
+ }
400
+ }
401
+
365
402
  private onDoneHandler = () => {
366
- this.setState({done: true});
403
+ this.setState({ done: true, error: false });
404
+ }
405
+
406
+ private onErrorHandler = () => {
407
+ this.setState({error: !this.state.error});
408
+ }
409
+
410
+ private onNextHandler = (val : boolean) => {
411
+ this.setState({next: val});
367
412
  }
368
413
 
369
414
  public render() {
370
415
  const {
371
416
  story,
417
+ project,
372
418
  api,
373
419
  handleError,
374
420
  createFreshBatch,
@@ -380,11 +426,13 @@ class PublishWizard extends React.PureComponent<IPublishWizardProps, IPublishWiz
380
426
  batchJobs
381
427
  } = this.props;
382
428
 
383
- const {screenIndex, status, selectedComposition, compositions, accessKey} = this.state;
429
+ const {screenIndex, status, selectedComposition, compositions, accessKey, error, nextStep} = this.state;
384
430
  const compName = compositions.find((comp) => {
385
431
  return comp.id === selectedComposition;
386
432
  })?.name || '';
387
433
 
434
+ const variables = (story) ? story.acts[project.actId].inventory : {};
435
+
388
436
  return (
389
437
  <div className='publish-wizard'>
390
438
  <div className = 'publish-wizard-header'>
@@ -401,7 +449,11 @@ class PublishWizard extends React.PureComponent<IPublishWizardProps, IPublishWiz
401
449
  isExport = {screenIndex === 7}
402
450
  api={api}
403
451
  story={story}
452
+ variables= {variables}
404
453
  onDone={this.onDoneHandler}
454
+ onError={this.onErrorHandler}
455
+ onNext={this.onNextHandler}
456
+ error={error}
405
457
  status={status}
406
458
  renderBatch={renderBatch}
407
459
  batchJobs={batchJobs}
@@ -413,6 +465,8 @@ class PublishWizard extends React.PureComponent<IPublishWizardProps, IPublishWiz
413
465
  getBatches={getBatches}
414
466
  getBatchExport={getBatchExport}
415
467
  importBatchFromCsv={importBatchFromCsv}
468
+ next={nextStep}
469
+ ref={this.emailWorkflow}
416
470
  />}
417
471
  {
418
472
  screenIndex === 5 &&
@@ -7,9 +7,14 @@ import { ExportToCsv } from 'export-to-csv';
7
7
  import { ICON_DOWNLOAD, ICON_UPLOAD } from '../../../constants/icons';
8
8
  import * as moment from 'moment';
9
9
  import { IImposiumAPI } from '../../../services/API';
10
+ import { bindActionCreators } from 'redux';
11
+ import { connect } from 'react-redux';
12
+ import SelectField from '../../select-field/SelectField';
13
+ import { updateAssociation, getBatch, setPage } from '../../../redux/actions/active-batch';
10
14
 
11
15
  interface IEmailWorkflowProps {
12
16
  story : any;
17
+ variables : any;
13
18
  createFreshBatch : (e : string) => any;
14
19
  getBatches : () => any;
15
20
  getBatchExport : (batchId : string) => any;
@@ -20,10 +25,19 @@ interface IEmailWorkflowProps {
20
25
  compositionId : string;
21
26
  compositionName : string;
22
27
  onDone : () => void;
28
+ onError : () => void;
29
+ error : boolean;
23
30
  api : IImposiumAPI;
24
31
  handleError : (e) => any;
25
32
  batchJobs : any;
26
33
  isExport ? : boolean;
34
+ updateAssociation : (api : IImposiumAPI, colIndex : number, newType : string, newAssociation : string) => any;
35
+ getBatch : (api : IImposiumAPI) => any;
36
+ activeBatch : any;
37
+ batchesList : any;
38
+ setPage : (page : number) => any;
39
+ onNext : ( val : boolean ) => void;
40
+ next : boolean;
27
41
  }
28
42
 
29
43
  interface IEmailWorkflowState {
@@ -32,6 +46,10 @@ interface IEmailWorkflowState {
32
46
  uploading : boolean;
33
47
  downloading : boolean;
34
48
  renderedBatch : boolean;
49
+ inventory : any[];
50
+ inventoryKeys : string[];
51
+ missingColumns : string;
52
+ isMissing : boolean;
35
53
  }
36
54
 
37
55
  class EmailWorkflow extends React.PureComponent<IEmailWorkflowProps, IEmailWorkflowState> {
@@ -45,8 +63,9 @@ class EmailWorkflow extends React.PureComponent<IEmailWorkflowProps, IEmailWorkf
45
63
  showTitle: false,
46
64
  useTextFile: false,
47
65
  useBom: false,
48
- useKeysAsHeaders: true,
66
+ useKeysAsHeaders: true
49
67
  };
68
+
50
69
  constructor(props) {
51
70
  super(props);
52
71
  this.state = {
@@ -55,14 +74,20 @@ class EmailWorkflow extends React.PureComponent<IEmailWorkflowProps, IEmailWorkf
55
74
  downloading : false,
56
75
  selectedBatchId: '',
57
76
  renderedBatch: false,
77
+ inventory: [],
78
+ inventoryKeys: [],
79
+ missingColumns: '',
80
+ isMissing: false,
58
81
  };
59
82
  this.hiddenFileInputRef = React.createRef();
60
83
  }
61
84
 
62
- public downloadSampleCsv() {
85
+ public componentDidMount() : void {
86
+ this.getInventory();
87
+ }
63
88
 
64
- const {name} = this.props.story;
65
- const {acts} = this.props.story;
89
+ private getInventory() {
90
+ const { acts } = this.props.story;
66
91
  const actsKeys : string[] = Object.keys(acts);
67
92
  let inventory : any = {};
68
93
 
@@ -71,6 +96,16 @@ class EmailWorkflow extends React.PureComponent<IEmailWorkflowProps, IEmailWorkf
71
96
  });
72
97
 
73
98
  const inventoryKeys : string[] = Object.keys(inventory).sort();
99
+
100
+ this.setState({inventory});
101
+ this.setState({inventoryKeys});
102
+ }
103
+
104
+ public downloadSampleCsv() {
105
+
106
+ const { name } = this.props.story;
107
+ const { inventory, inventoryKeys } = this.state;
108
+
74
109
  const maskConfig : any[] = inventoryKeys.map((currKey : string) => ({
75
110
  id: currKey,
76
111
  name: inventory[currKey].name,
@@ -124,23 +159,48 @@ class EmailWorkflow extends React.PureComponent<IEmailWorkflowProps, IEmailWorkf
124
159
  });
125
160
  }
126
161
 
162
+ private getBatch = () => {
163
+ const { api } = this.props;
164
+ this.props.setPage(1);
165
+ this.props.getBatch(api);
166
+ }
167
+
168
+ public proceedWithExport() {
169
+ const { renderBatch, activeBatch: { data: { id, name }} } = this.props;
170
+ renderBatch(id, name).then(() => {
171
+ this.setState({renderedBatch: true});
172
+ }).catch((e) => {
173
+ this.props.handleError(copy.publish.renderBatchFailed);
174
+ this.setState({renderedBatch: false});
175
+ throw e;
176
+ });
177
+ }
178
+
127
179
  private importBatch({story_id, id, name, uploadEvt}) {
128
- const {accessKey, compositionId, isExport, handleError} = this.props;
180
+ const {accessKey, compositionId, isExport, onError} = this.props;
129
181
  this.props.importBatchFromCsv(story_id, id, name, uploadEvt.target.files[0], accessKey, compositionId, !isExport, isExport)
130
182
  .then(() => {
183
+
184
+ if (this.props.batchJobs.missing.length > 0 ) {
185
+ onError();
186
+ }
187
+
131
188
  this.setState({uploadComplete: true, uploading: false});
132
- if (isExport) {
189
+ this.getBatch();
190
+
191
+ if (isExport && Object.keys(this.props.batchJobs.missing).length === 0) {
133
192
  this.renderBatch({id, name});
134
193
  }
135
194
  })
136
195
  .catch((e) => {
137
- const {status, data} = e.response;
138
-
139
- if (status === 400) {
140
- handleError(copy.publish.missingColumns);
141
- alert(copy.publish.csvMissingColumns + data.error);
142
- this.hiddenFileInputRef.current.value = '';
143
- }
196
+ // const {status, data} = e.response;
197
+ // if (status === 400) {
198
+ // handleError(copy.publish.missingColumns);
199
+ // this.setState({csvError: true});
200
+ // this.setState({csvErrorString: copy.publish.csvMissingColumns + data.error})
201
+ // // this.getBatch(id);
202
+ // this.hiddenFileInputRef.current.value = '';
203
+ // }
144
204
  this.setState({uploadComplete: false, uploading: false});
145
205
  throw e;
146
206
  });
@@ -168,6 +228,7 @@ class EmailWorkflow extends React.PureComponent<IEmailWorkflowProps, IEmailWorkf
168
228
  }
169
229
  this.props.getBatchExport(selectedBatchId).then(() => {
170
230
  this.props.onDone();
231
+ // this.props.onError();
171
232
  this.setState({
172
233
  downloading: false
173
234
  });
@@ -176,9 +237,10 @@ class EmailWorkflow extends React.PureComponent<IEmailWorkflowProps, IEmailWorkf
176
237
  }
177
238
 
178
239
  public renderLabel() {
179
- const {isExport, batchJobs} = this.props;
180
- const {renderedBatch, selectedBatchId, uploading, uploadComplete} = this.state;
181
- if (isExport && uploadComplete && !renderedBatch) {
240
+ const { isExport, batchJobs } = this.props;
241
+ const { renderedBatch, selectedBatchId, uploading, uploadComplete } = this.state;
242
+
243
+ if (isExport && uploadComplete && !renderedBatch && Object.keys(this.props.batchJobs.missing).length === 0 ) {
182
244
  return (
183
245
  <div className={'progress-bar'}>
184
246
  <progress
@@ -195,9 +257,134 @@ class EmailWorkflow extends React.PureComponent<IEmailWorkflowProps, IEmailWorkf
195
257
  return <span>{ICON_UPLOAD}&nbsp;{copy.publish.uploadCsv}</span>;
196
258
  }
197
259
 
260
+ private onAssociationChange(colIndex : any, e : any, stillMissingCols : boolean) {
261
+
262
+ const {activeBatch: {data : { columns }}} = this.props;
263
+
264
+ // Change the previous association back to static
265
+ const previousColumn = columns.findIndex((col) => {
266
+ return col.detail === e;
267
+ });
268
+ if (previousColumn !== -1) {
269
+ this.props.updateAssociation(this.props.api, previousColumn, 'Static', null);
270
+ }
271
+
272
+ // Set the new association
273
+ this.props.updateAssociation(this.props.api, parseInt(colIndex, 10), 'UGC', e);
274
+
275
+ this.props.onNext(stillMissingCols);
276
+ }
277
+
278
+ private renderColumnMap() {
279
+
280
+ const { activeBatch: {data : { columns }}, batchJobs: { missing }, variables } = this.props;
281
+
282
+ if (missing.length >= 0 && columns) {
283
+
284
+ const varArray = Object.keys(variables).map((key) => {
285
+
286
+ // disable options which are already aligned
287
+ const opt = {...variables[key]};
288
+
289
+ return opt;
290
+ });
291
+
292
+ const colArray = Object.keys(columns).map((i) => {
293
+ return columns[i];
294
+ });
295
+
296
+ const colOpts = colArray.filter((c : any) => {
297
+ if (c.from_import === 0 ) {
298
+ return false;
299
+ }
300
+
301
+ return true;
302
+ }).map((c, i) => {
303
+
304
+ const opt : any = {
305
+ value: i,
306
+ label: c.name
307
+ };
308
+ const alignedColIdx = varArray.findIndex((v) => {
309
+ return c.detail === v.id;
310
+ });
311
+
312
+ if (alignedColIdx !== -1) {
313
+ opt.disabled = true;
314
+ }
315
+ return opt;
316
+ });
317
+
318
+ // check to see if any required variables are missing
319
+ let stillMissingCols = false;
320
+ for (const v of varArray) {
321
+ if (!v.optional) {
322
+ const selectedCol = colArray.findIndex((c : any) => {
323
+ return c.detail === v.id;
324
+ });
325
+ if (selectedCol === -1) {
326
+ stillMissingCols = true;
327
+ break;
328
+ }
329
+ }
330
+ }
331
+
332
+ // add an empty option to the columns
333
+ colOpts.unshift('');
334
+
335
+ const missingColumn = Object.keys(missing).map((m) => missing[m]);
336
+ const showMissing = missingColumn.join();
337
+
338
+ if (colOpts.length < varArray.length) {
339
+ return (
340
+ <><HRule/><p className='missingColumns'>{`${copy.publish.csvMissingColumns} ${showMissing}`}</p></>
341
+ );
342
+ } else {
343
+ return (
344
+ <div className = 'align-columns' >
345
+ <HRule/>
346
+ <p className='match-columns'>{`${copy.publish.csvMatchColumns}`}</p>
347
+
348
+ <table>
349
+ <thead>
350
+ <tr>
351
+ <th>Variable</th>
352
+ <th>CSV Column</th>
353
+ </tr>
354
+ </thead>
355
+ <tbody>
356
+ {
357
+ varArray.map((v, i) => {
358
+
359
+ const selectedCol = colArray.findIndex((c : any) => {
360
+ return c.detail === v.id;
361
+ });
362
+
363
+ const selectedVal = (selectedCol !== undefined && selectedCol !== null) ? selectedCol : '';
364
+ const rClass = (!v.optional) ? 'required' : '';
365
+
366
+ return <tr key = {`var-${v.id}`} className = {`${rClass}`}>
367
+ <td className='variable'>
368
+ <p>{v.name} { (!v.optional) ? <span style={{ color: 'red' }}> * </span> : null }</p>
369
+ </td>
370
+ <td>
371
+ <SelectField options={colOpts} value={selectedVal} onChange={(c) => this.onAssociationChange(c, v.id, stillMissingCols)} />
372
+ </td>
373
+ </tr>;
374
+ })
375
+ }
376
+ </tbody>
377
+ </table>
378
+ </div>
379
+ );
380
+ }
381
+
382
+ }
383
+ }
384
+
198
385
  public render() {
199
386
  const {uploadComplete, uploading, downloading, renderedBatch, selectedBatchId} = this.state;
200
- const {isExport} = this.props;
387
+ const { isExport, batchJobs: { missing, renders } } = this.props;
201
388
 
202
389
  const emailOptions = [
203
390
  {
@@ -214,16 +401,27 @@ class EmailWorkflow extends React.PureComponent<IEmailWorkflowProps, IEmailWorkf
214
401
  ];
215
402
  const downloadCsvLabel = (downloading) ? <span><Spinner/>&nbsp;{copy.publish.exporting}</span> : <span>{ICON_DOWNLOAD}&nbsp;{copy.publish.btnDownload}</span>;
216
403
 
217
- const downloadCSV = (
218
- <div>
404
+ const reRenderLabel = <div className={'progress-bar'}>
405
+ <progress
406
+ max = {EmailWorkflow.PROGRESS_MAX_VALUE}
407
+ value = {renders[selectedBatchId]}
408
+ />
409
+ </div>;
410
+
411
+ const downloadCSVDiv = <div>
219
412
  <h2>{uploadComplete ? copy.publish.downloadLink : copy.publish.generatingLink}</h2>
220
413
  <HRule/>
221
414
  <p>{uploadComplete ? copy.publish.downloadDesc : copy.publish.generatingLinkDesc}</p>
222
- <BigButton
223
- label={downloadCsvLabel}
224
- disabled = {downloading}
225
- onClick={() => this.downloadCsv()}/>
226
- </div>
415
+
416
+ { isExport && uploadComplete && !renderedBatch ?
417
+ <BigButton label={reRenderLabel} disabled={isExport}/> :
418
+ <BigButton label={downloadCsvLabel} disabled = {downloading} onClick={() => this.downloadCsv()}/>}
419
+ </div>;
420
+
421
+ const downloadCSV = (<>
422
+ {this.props.error ? ( this.props.next ? downloadCSVDiv : '' ) : downloadCSVDiv}
423
+ {this.renderColumnMap()}
424
+ </>
227
425
  );
228
426
 
229
427
  const uploadCSV = <div>
@@ -238,7 +436,6 @@ class EmailWorkflow extends React.PureComponent<IEmailWorkflowProps, IEmailWorkf
238
436
  onChange={this.doUploadCsv}/>
239
437
  <div className = 'link-wrapper'>
240
438
  {emailOptions?.map((option, index) => {
241
-
242
439
  return <BigButton
243
440
  label={<span>{option.label}</span>}
244
441
  onClick={option.onClick}
@@ -248,7 +445,7 @@ class EmailWorkflow extends React.PureComponent<IEmailWorkflowProps, IEmailWorkf
248
445
  </div>
249
446
  </div>;
250
447
 
251
- return (isExport ? renderedBatch : uploadComplete) ? downloadCSV : uploadCSV;
448
+ return (( isExport && Object.keys(missing).length === 0 ) ? renderedBatch : uploadComplete) ? downloadCSV : uploadCSV;
252
449
  }
253
450
  }
254
451
 
@@ -256,4 +453,16 @@ const alphaNumeric = (str) => {
256
453
  return str.replace(/\W/g, '');
257
454
  };
258
455
 
259
- export default EmailWorkflow;
456
+ const mapDispatchToProps = (dispatch) : any => {
457
+ return bindActionCreators({
458
+ updateAssociation, getBatch, setPage
459
+ }, dispatch);
460
+ };
461
+
462
+ const mapStateToProps = (state) : any => {
463
+ return {
464
+ activeBatch: state.activeBatch,
465
+ batchesList: state.batchesList,
466
+ };
467
+ };
468
+ export default connect(mapStateToProps, mapDispatchToProps, null, { withRef: true })(EmailWorkflow);
@@ -62,7 +62,7 @@ class SelectField extends React.PureComponent<ISelectFieldProps, {}> {
62
62
 
63
63
  public render() {
64
64
 
65
- const {label, showCopy, placeholder, options, value, width, buttons, selectRef, disable, tooltip, info, labelPosition, disableFirst} = this.props;
65
+ const {label, showCopy, options, value, width, buttons, selectRef, disable, tooltip, info, labelPosition, disableFirst} = this.props;
66
66
  let opts = [];
67
67
 
68
68
  const selectValue = (value === null || value === undefined) ? '' : value;
@@ -91,8 +91,7 @@ class SelectField extends React.PureComponent<ISelectFieldProps, {}> {
91
91
 
92
92
  if (options) {
93
93
  opts = options.map((option , i) => {
94
-
95
- const disabled = (i === 0 && disableFirst) ? true : false;
94
+ const disabled = (i === 0 && disableFirst || (option.disabled !== undefined && option.disabled)) ? true : false;
96
95
  const optionValue = (option.value !== undefined) ? option.value : option;
97
96
  const optionLabel = (option.label !== undefined) ? option.label : option;
98
97
  return <option disabled = {disabled} key = {`option-${optionValue}`} value = {optionValue}>{optionLabel}</option>;
package/constants/copy.ts CHANGED
@@ -138,6 +138,7 @@ export const publish = {
138
138
  importingData: 'Importing Data... (please wait)',
139
139
  downloadDesc: 'Your CSV data file is ready to download with an embed code you can import into your email platform to send out.',
140
140
  csvMissingColumns: 'Warning: Some of your column names did not match up with the variables in the story. Please ensure these variables line up and then re-upload the csv: ',
141
+ csvMatchColumns: 'Please match the column(s) provided to the story variables:',
141
142
  // export
142
143
  exportBatchOfVideo: 'Export batch of videos',
143
144
 
@@ -159,6 +160,8 @@ export const publish = {
159
160
  btnSkip: 'Skip',
160
161
  btnBack: 'Back',
161
162
  btnFinished: 'Done',
163
+ btnCancel: 'Cancel',
164
+ btnNext: 'Next',
162
165
  noCompErrorTitle: 'No Compositions Found',
163
166
  noCompErrorDesc: `You must have at least 1 composition on your story to render and distribute videos.`
164
167
  };