@capillarytech/creatives-library 7.9.13 → 7.9.15

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.
Files changed (45) hide show
  1. package/assets/group.png +0 -0
  2. package/components/Card/index.js +1 -1
  3. package/components/Card/tests/__snapshots__/index.test.js.snap +22 -0
  4. package/components/Card/tests/index.test.js +19 -7
  5. package/containers/App/constants.js +2 -1
  6. package/helpers/intl-enzym-test-helpers.js +40 -0
  7. package/index.js +6 -0
  8. package/package.json +3 -2
  9. package/routes.js +5 -0
  10. package/services/api.js +9 -15
  11. package/utils/common.js +17 -4
  12. package/v2Components/CapTagList/index.js +38 -9
  13. package/v2Components/CapTagList/messages.js +4 -0
  14. package/v2Containers/Cap/constants.js +2 -0
  15. package/v2Containers/Cap/index.js +9 -2
  16. package/v2Containers/Cap/reducer.js +5 -1
  17. package/v2Containers/Cap/selectors.js +10 -1
  18. package/v2Containers/CreativesContainer/SlideBoxContent.js +10 -4
  19. package/v2Containers/CreativesContainer/SlideBoxHeader.js +6 -1
  20. package/v2Containers/CreativesContainer/messages.js +4 -0
  21. package/v2Containers/FTP/index.js +14 -4
  22. package/v2Containers/Sms/Create/index.js +5 -6
  23. package/v2Containers/Sms/Edit/sagas.js +1 -1
  24. package/v2Containers/SmsTrai/Create/actions.js +25 -0
  25. package/v2Containers/SmsTrai/Create/constants.js +74 -0
  26. package/v2Containers/SmsTrai/Create/index.js +681 -0
  27. package/v2Containers/SmsTrai/Create/index.scss +92 -0
  28. package/v2Containers/SmsTrai/Create/messages.js +116 -0
  29. package/v2Containers/SmsTrai/Create/reducer.js +43 -0
  30. package/v2Containers/SmsTrai/Create/sagas.js +36 -0
  31. package/v2Containers/SmsTrai/Create/selectors.js +26 -0
  32. package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +10351 -0
  33. package/v2Containers/SmsTrai/Create/tests/index.test.js +77 -0
  34. package/v2Containers/SmsTrai/Create/tests/mockData.js +58 -0
  35. package/v2Containers/SmsTrai/Edit/constants.js +16 -0
  36. package/v2Containers/SmsTrai/Edit/index.js +483 -0
  37. package/v2Containers/SmsTrai/Edit/messages.js +58 -0
  38. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +16103 -0
  39. package/v2Containers/SmsTrai/Edit/tests/index.test.js +61 -0
  40. package/v2Containers/SmsTrai/Edit/tests/mockData.js +36 -0
  41. package/v2Containers/SmsWrapper/index.js +75 -0
  42. package/v2Containers/SmsWrapper/tests/index.test.js +10 -0
  43. package/v2Containers/Templates/_templates.scss +2 -0
  44. package/v2Containers/Templates/index.js +89 -27
  45. package/v2Containers/Templates/messages.js +16 -0
@@ -0,0 +1,681 @@
1
+ import PropTypes from 'prop-types';
2
+ import React, { useState } from 'react';
3
+ import { FormattedMessage, injectIntl } from 'react-intl';
4
+ import { createStructuredSelector } from 'reselect';
5
+ import styled from 'styled-components';
6
+ import Dropzone from 'react-dropzone';
7
+ import { bindActionCreators } from 'redux';
8
+ import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
9
+ import CapHeading from '@capillarytech/cap-ui-library/CapHeading';
10
+ import CapRow from '@capillarytech/cap-ui-library/CapRow';
11
+ import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
12
+ import CapButton from '@capillarytech/cap-ui-library/CapButton';
13
+ import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
14
+ import CapError from '@capillarytech/cap-ui-library/CapError';
15
+ import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
16
+ import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
17
+ import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
18
+ import Papa from 'papaparse';
19
+ import cloneDeep from 'lodash/cloneDeep';
20
+ import {
21
+ CAP_SPACE_12,
22
+ CAP_SPACE_16,
23
+ CAP_SPACE_24,
24
+ CAP_SPACE_32,
25
+ CAP_WHITE,
26
+ CAP_RED01,
27
+ CAP_PRIMARY,
28
+ } from '@capillarytech/cap-ui-library/styled/variables';
29
+ import './index.scss';
30
+ import dragdropImage from '../../../assets/group.png';
31
+ import * as smsTraiActions from './actions';
32
+ import {
33
+ MANDATORY_COLUMNS,
34
+ HEADER_ALIASES,
35
+ SAMPLE_CSV_DATA,
36
+ DOWNLOAD_ISSUES_CSV,
37
+ SAMPLE_CSV,
38
+ CAPILLARY_REJECTION_REASON,
39
+ APPROVAL_STATUS,
40
+ APPROVED,
41
+ TEMPLATE_ID_ALIASES,
42
+ TEMPLATE_MESSAGE_ALIASES,
43
+ SAVED_COLUMNS,
44
+ MAPPED_SAVED_COLUMN,
45
+ SMS,
46
+ } from './constants';
47
+ import messages from './messages';
48
+ import withCreatives from '../../../hoc/withCreatives';
49
+
50
+ export const SmsTraiCreate = (props) => {
51
+ const { intl, actions, onCreateComplete } = props;
52
+ const { formatMessage } = intl;
53
+ const [files, setFiles] = useState([]);
54
+ const [errorText, setErrorText] = useState('');
55
+ const [dropZone, setDropZone] = useState(true);
56
+ const [previewFileIndex, setPreviewFileIndex] = useState(null);
57
+ const [isFileDropped, setIsFileDropped] = useState(false);
58
+ const [totalFailCount, setTotalFailCount] = useState(0);
59
+ const [saveTemplate, updateSaveTemplate] = useState(false);
60
+
61
+ const SMSTraiFooter = styled.div`
62
+ background-color: ${CAP_WHITE};
63
+ position: fixed;
64
+ bottom: 0;
65
+ width: 100%;
66
+ margin-left: -32px;
67
+ padding: ${CAP_SPACE_32} ${CAP_SPACE_24};
68
+ .ant-btn {
69
+ margin-right: ${CAP_SPACE_16};
70
+ }
71
+ }
72
+ `;
73
+
74
+ let uploadValidated = false;
75
+
76
+ const getIndexofColumn = (allColumnNames, columnName) =>
77
+ allColumnNames.findIndex(
78
+ (column) => column.toLowerCase().trim() === columnName,
79
+ );
80
+
81
+ const checkCSVRowsValidation = (transformedResults, selectedHeaderAlias) => {
82
+ const headerIndex = getIndexofColumn(
83
+ transformedResults[0],
84
+ selectedHeaderAlias,
85
+ );
86
+ const approvalIndex = getIndexofColumn(
87
+ transformedResults[0],
88
+ APPROVAL_STATUS,
89
+ );
90
+
91
+ for (let row = 1; row < transformedResults.length; row++) {
92
+ const errorArray = [];
93
+ const headerErrorArray = [];
94
+ const approvalErrorArray = [];
95
+ let rejectionReasons = '';
96
+ //adding rejection reasons
97
+ transformedResults[row].forEach((cell, index) => {
98
+ if (index === headerIndex && cell.length < 6) {
99
+ headerErrorArray.push(
100
+ `${selectedHeaderAlias.toUpperCase()} is mandatory and it should be 6 characters. `,
101
+ );
102
+ } else if (index === approvalIndex && cell.toLowerCase() !== APPROVED) {
103
+ approvalErrorArray.push(`Template is not approved. `);
104
+ } else if (!cell && index !== headerIndex && index !== approvalIndex) {
105
+ errorArray.push(`"${transformedResults[0][index]}"`);
106
+ }
107
+ });
108
+
109
+ if (errorArray.length !== 0) {
110
+ rejectionReasons = `${rejectionReasons} Please include these mandatory fields: ${errorArray.join(
111
+ ' ',
112
+ )}.`;
113
+ }
114
+ if (headerErrorArray.length !== 0 || approvalErrorArray.length !== 0) {
115
+ rejectionReasons = `${rejectionReasons} ${headerErrorArray.toString()} ${approvalErrorArray.toString()}`;
116
+ }
117
+ transformedResults[row].push(rejectionReasons);
118
+ //if there are rejected reasons present in this row at column 6(where we store rejected reasons), consider it as failure
119
+ if (transformedResults[row][6]) {
120
+ setTotalFailCount((prev) => prev + 1);
121
+ }
122
+ }
123
+ uploadValidated = true;
124
+ return transformedResults;
125
+ };
126
+
127
+ const checkCSVValidation = (results, file) => {
128
+ if (file?.name?.split('.').pop() === 'csv') {
129
+ const mandatoryColNotPresent = [];
130
+ const headerAliasPresent = [];
131
+ const templateIdAliasPresent = [];
132
+ const templateMessageAliasPresent = [];
133
+ const colCounts = {};
134
+ //removing last empty row if each cell is empty
135
+ if (results?.data[results.data.length - 1]?.every((cell) => !cell)) {
136
+ results.data.pop();
137
+ }
138
+ const fileLen = results?.data?.length || 0;
139
+ // at least 1 records should be available
140
+ if (fileLen < 2) {
141
+ setErrorText(formatMessage(messages.fileMinlengthError));
142
+ return;
143
+ }
144
+ // more then 500 records are not available.
145
+ if (fileLen > 501) {
146
+ setErrorText(formatMessage(messages.fileMaxlengthError));
147
+ return;
148
+ }
149
+ // mandatory 3 header name should be available.
150
+ const cols = getAllColumnNames(results.data[0]);
151
+
152
+ for (let i = 0; i < MANDATORY_COLUMNS.length; i++) {
153
+ if (!cols.includes(MANDATORY_COLUMNS[i])) {
154
+ mandatoryColNotPresent.push(MANDATORY_COLUMNS[i]);
155
+ }
156
+ }
157
+ if (mandatoryColNotPresent.length > 0) {
158
+ setErrorText(
159
+ formatMessage(messages.invalidFileErrorDescription, {
160
+ columns: mandatoryColNotPresent.join(','),
161
+ }),
162
+ );
163
+ return;
164
+ }
165
+
166
+ //one of the mandatory header alias should be available.
167
+ for (let i = 0; i < HEADER_ALIASES.length; i++) {
168
+ if (cols.includes(HEADER_ALIASES[i])) {
169
+ headerAliasPresent.push(HEADER_ALIASES[i]);
170
+ }
171
+ }
172
+ if (headerAliasPresent.length === 0) {
173
+ setErrorText(
174
+ formatMessage(messages.headerAliasErrorDescription, {
175
+ headerAliases: HEADER_ALIASES.join(' / '),
176
+ }),
177
+ );
178
+ return;
179
+ }
180
+ //one of the mandatory template id alias should be available.
181
+ for (let i = 0; i < TEMPLATE_ID_ALIASES.length; i++) {
182
+ if (cols.includes(TEMPLATE_ID_ALIASES[i])) {
183
+ templateIdAliasPresent.push(TEMPLATE_ID_ALIASES[i]);
184
+ }
185
+ }
186
+ if (templateIdAliasPresent.length === 0) {
187
+ setErrorText(
188
+ formatMessage(messages.templateIdAliasErrorDescription, {
189
+ templateIdAliases: TEMPLATE_ID_ALIASES.join(' / '),
190
+ }),
191
+ );
192
+ return;
193
+ }
194
+ //one of the mandatory template message alias should be available.
195
+ for (let i = 0; i < TEMPLATE_MESSAGE_ALIASES.length; i++) {
196
+ if (cols.includes(TEMPLATE_MESSAGE_ALIASES[i])) {
197
+ templateMessageAliasPresent.push(TEMPLATE_MESSAGE_ALIASES[i]);
198
+ }
199
+ }
200
+ if (templateMessageAliasPresent.length === 0) {
201
+ setErrorText(
202
+ formatMessage(messages.templateMessageAliasErrorDescription, {
203
+ templateMessageAliases: TEMPLATE_MESSAGE_ALIASES.join(' / '),
204
+ }),
205
+ );
206
+ return;
207
+ }
208
+ // count of each col to validate blank and repetitive cols
209
+ for (const num of cols) {
210
+ colCounts[num] = colCounts[num] ? colCounts[num] + 1 : 1;
211
+ }
212
+
213
+ const repetitiveColsArray = Object.keys(
214
+ Object.fromEntries(
215
+ Object.entries(colCounts).filter(
216
+ ([key, value]) => key !== '' && value > 1,
217
+ ),
218
+ ),
219
+ );
220
+ //throw validation if repetitive columns present
221
+ if (repetitiveColsArray.length !== 0) {
222
+ setErrorText(
223
+ formatMessage(messages.repetitiveColsErrorDescription, {
224
+ repetitiveCols: repetitiveColsArray,
225
+ }),
226
+ );
227
+ return;
228
+ }
229
+ //perform required data transformation
230
+ return dataTransformHandler(cols, results, headerAliasPresent);
231
+ } else {
232
+ setErrorText(formatMessage(messages.nonCsvErrorDescription));
233
+ }
234
+ };
235
+
236
+ const dataTransformHandler = (cols, results, headerAliasPresent) => {
237
+ //getting the index of the cols for 6 mandatory cols and also taking care of alias of headers of mandatory columns
238
+ const indexArray = [];
239
+ for (let i = 0; i < cols.length; i++) {
240
+ if (
241
+ MANDATORY_COLUMNS.includes(cols[i]) ||
242
+ HEADER_ALIASES.includes(cols[i]) ||
243
+ TEMPLATE_ID_ALIASES.includes(cols[i]) ||
244
+ TEMPLATE_MESSAGE_ALIASES.includes(cols[i])
245
+ ) {
246
+ indexArray.push(i);
247
+ }
248
+ }
249
+ //transforming data to keep only mandatory cols data using their index
250
+ const transformedResults = [];
251
+ for (const key in results.data) {
252
+ transformedResults.push(indexArray.map((i) => results.data[key][i]));
253
+ }
254
+ //add rejection header
255
+ transformedResults[0].push(CAPILLARY_REJECTION_REASON);
256
+ return checkCSVRowsValidation(transformedResults, headerAliasPresent[0]);
257
+ };
258
+
259
+ //returns the headers(first row) of the uploaded file in lowercase
260
+ const getAllColumnNames = (data) => data.map((x) => x.toLowerCase().trim());
261
+
262
+ const onDrop = (droppedFile) => {
263
+ // Sets isFileDropped state to true to load the spinner while the file is being parsed.
264
+ setIsFileDropped(true);
265
+
266
+ Papa.parse(droppedFile[0], {
267
+ complete: (results, file) => {
268
+ setIsFileDropped(false); // Stop the spinner
269
+ const validatedResults = checkCSVValidation(results, file);
270
+ const selectedFiles = cloneDeep(files);
271
+ if (uploadValidated) {
272
+ //remove capillary rejection reason column if already present on reupload
273
+ //ex: user reuploads a csv which was earlier validated and rejection reasons were added for failed templates
274
+ const rejectionIndex = getAllColumnNames(results.data[0]).indexOf(
275
+ CAPILLARY_REJECTION_REASON.toLowerCase(),
276
+ );
277
+ if (rejectionIndex !== -1) {
278
+ results.data.forEach((row) => {
279
+ row.splice(rejectionIndex, 1);
280
+ });
281
+ }
282
+ //adding validated rejection reason column to original results
283
+ for (let i = 0; i < validatedResults.length; i++) {
284
+ results.data[i].push(validatedResults[i][6]);
285
+ }
286
+ updateSaveTemplate(true);
287
+ }
288
+ selectedFiles.push({
289
+ file,
290
+ fileResult: results,
291
+ columnMapping: [],
292
+ });
293
+
294
+ setFiles(selectedFiles);
295
+ setPreviewFileIndex(selectedFiles.length - 1);
296
+ setDropZone(false);
297
+ },
298
+ });
299
+ };
300
+
301
+ const deleteCSV = () => {
302
+ setFiles([]);
303
+ setTotalFailCount(0);
304
+ setErrorText('');
305
+ setIsFileDropped(false);
306
+ setPreviewFileIndex(null);
307
+ setDropZone(true);
308
+ updateSaveTemplate(false);
309
+ };
310
+
311
+ //common function to handle csv downloads
312
+ const downloadHandler = (event, data, downloadedFileName) => {
313
+ event.target.setAttribute(
314
+ 'href',
315
+ `data:text/csv;charset=utf-8,${encodeURIComponent(data)}`,
316
+ );
317
+ event.target.setAttribute('download', downloadedFileName);
318
+ };
319
+
320
+ const downloadSampleCSV = (event) => {
321
+ const csvContent = SAMPLE_CSV_DATA.map((e) => e.join(',')).join('\n');
322
+ downloadHandler(event, csvContent, SAMPLE_CSV);
323
+ };
324
+
325
+ const downloadIssuesCSV = (event) => {
326
+ let csvContent = [];
327
+ const tempArray = [];
328
+ const fileData = files[0].fileResult.data;
329
+ const rejectionIndex = fileData[0].indexOf(CAPILLARY_REJECTION_REASON);
330
+
331
+ if (rejectionIndex !== -1) {
332
+ tempArray.push(fileData[0]);
333
+ for (let row = 1; row < fileData.length; row++) {
334
+ if (fileData[row][rejectionIndex]) {
335
+ tempArray.push(fileData[row]);
336
+ }
337
+ }
338
+ csvContent = tempArray
339
+ .map((e) => e.join(','))
340
+ .map((e) => e.replace(/(\r\n|\n|\r)/gm, ' '))
341
+ .join('\n');
342
+ }
343
+ downloadHandler(event, csvContent, DOWNLOAD_ISSUES_CSV);
344
+ };
345
+
346
+ const createCallback = ({ errorMessage }) => {
347
+ if (!errorMessage) {
348
+ CapNotification.success({
349
+ message: formatMessage(messages.smsCreateNotification),
350
+ });
351
+ actions.clearCreateResponse();
352
+ } else {
353
+ CapNotification.error({
354
+ message: errorMessage,
355
+ });
356
+ }
357
+ };
358
+
359
+ const onDoneCallback = () => {
360
+ //start spinner
361
+ setIsFileDropped(true);
362
+ const fileData = files[0].fileResult.data;
363
+ const rejectionIndex = fileData[0].indexOf(CAPILLARY_REJECTION_REASON);
364
+ const tempArray = [];
365
+ tempArray.push(fileData[0]);
366
+
367
+ // filter out non rejected rows
368
+ for (let row = 1; row < fileData.length; row++) {
369
+ if (!fileData[row][rejectionIndex]) {
370
+ tempArray.push(fileData[row]);
371
+ }
372
+ }
373
+
374
+ // Filter out the columns index that needs to save
375
+ const cols = getAllColumnNames(tempArray[0]);
376
+ const indexArray = [];
377
+ for (let i = 0; i < cols.length; i++) {
378
+ if (
379
+ SAVED_COLUMNS.includes(cols[i]) ||
380
+ HEADER_ALIASES.includes(cols[i]) ||
381
+ TEMPLATE_ID_ALIASES.includes(cols[i]) ||
382
+ TEMPLATE_MESSAGE_ALIASES.includes(cols[i])
383
+ ) {
384
+ indexArray.push(i);
385
+ }
386
+ }
387
+
388
+ //transforming data to keep only saved cols data using their index
389
+ const transformedResults = [];
390
+ for (const key in tempArray) {
391
+ transformedResults.push(
392
+ indexArray.map((i) => {
393
+ if (key === '0') {
394
+ return MAPPED_SAVED_COLUMN[tempArray[key][i]];
395
+ }
396
+ return tempArray[key][i];
397
+ }),
398
+ );
399
+ }
400
+
401
+ // prepare data based on API
402
+ const savedData = [];
403
+ transformedResults.forEach((arr, index) => {
404
+ if (index !== 0) {
405
+ const obj = {};
406
+ arr.forEach((data, i) => {
407
+ obj[transformedResults[0][i]] = arr[i];
408
+ });
409
+ obj['unicode-validity'] = false;
410
+ obj['updated-sms-editor'] = '';
411
+ obj['var-mapped'] = {};
412
+ const history = obj;
413
+ history.tabKey = 1;
414
+ savedData.push({
415
+ versions: { base: obj },
416
+ history: [history],
417
+ type: SMS,
418
+ traiEnable: true,
419
+ name: obj.template_name,
420
+ });
421
+ }
422
+ });
423
+ actions.createSMSTRAITemplates(
424
+ { templates: savedData },
425
+ (resp, errorMessage) => {
426
+ createCallback({ errorMessage });
427
+ onCreateComplete();
428
+ },
429
+ );
430
+ };
431
+
432
+ const isDisableDone = () => {
433
+ //only if there is a file which is validated and has passed templates
434
+ //the done is not disabled otherwise it is disabled
435
+ if (
436
+ previewFileIndex !== null &&
437
+ saveTemplate &&
438
+ files[0].fileResult.data.length - 1 - totalFailCount >= 1
439
+ ) {
440
+ return false;
441
+ }
442
+ return true;
443
+ };
444
+
445
+ const onCancel = () => {
446
+ setFiles([]);
447
+ updateSaveTemplate(false);
448
+ };
449
+
450
+ const InstructionComp = (inst) => (
451
+ <CapRow className="instruction">
452
+ <CapColumn span={3}>
453
+ <span className="instruction-num">{inst.number}</span>
454
+ </CapColumn>
455
+ <CapColumn span={21}>{inst.message}</CapColumn>
456
+ </CapRow>
457
+ );
458
+
459
+ const fileData = files[previewFileIndex] || {};
460
+ const { fileResult, file } = fileData;
461
+ return (
462
+ <>
463
+ <CapRow span={24} className="upload-type-group-create-container">
464
+ <CapColumn span={16}>
465
+ <CapHeading type="h4" className="upload-file-title">
466
+ {formatMessage(messages.uploadFile)}
467
+ </CapHeading>
468
+ {previewFileIndex === null && dropZone && (
469
+ <>
470
+ <CapSpin spinning={isFileDropped}>
471
+ <Dropzone onDrop={onDrop} onFileDialogCancel={onCancel}>
472
+ {({ getRootProps, getInputProps }) => (
473
+ <div {...getRootProps()} className="file-drop-zone">
474
+ <input {...getInputProps()} />
475
+ <div>
476
+ <img
477
+ src={dragdropImage}
478
+ role="presentation"
479
+ alt="dropzone"
480
+ />
481
+ <div className="drop-zone-text">
482
+ <CapHeading type="h2" className="drop-zone-text-1">
483
+ {formatMessage(messages.dropCsvFile)}
484
+ </CapHeading>
485
+ <CapHeading type="h3" className="drop-zone-text-2">
486
+ {formatMessage(messages.or)}
487
+ </CapHeading>
488
+ <CapButton
489
+ type="secondary"
490
+ className="drop-zone-text-3"
491
+ >
492
+ {formatMessage(messages.selectFileFromComputer)}
493
+ </CapButton>
494
+ </div>
495
+ </div>
496
+ </div>
497
+ )}
498
+ </Dropzone>
499
+ </CapSpin>
500
+ </>
501
+ )}
502
+ {previewFileIndex !== null && (
503
+ <>
504
+ <CapRow span={24} className="upload-type-group-create-container">
505
+ <div className="upload-file-preview">
506
+ <CapDivider
507
+ type="horizontal"
508
+ className="upload-file-preview-divider-1"
509
+ />
510
+ <CapRow>
511
+ <CapColumn span={13}>
512
+ <CapLabel.CapLabelInline type="label9">
513
+ {file.name}
514
+ </CapLabel.CapLabelInline>
515
+ <CapLabel.CapLabelInline
516
+ type="label1"
517
+ className="number-of-records"
518
+ >
519
+ (
520
+ {formatMessage(messages.numberOfRecords, {
521
+ value: fileResult.data.length - 1,
522
+ })}
523
+ )
524
+ </CapLabel.CapLabelInline>
525
+ </CapColumn>
526
+ {errorText && (
527
+ <>
528
+ <CapColumn
529
+ span={10}
530
+ className="upload-failed-container"
531
+ >
532
+ <CapLabel
533
+ type="label9"
534
+ className="upload-failed-message"
535
+ >
536
+ {formatMessage(messages.failedToUpload)}
537
+ </CapLabel>
538
+ <CapIcon
539
+ type="warning"
540
+ className="upload-failed-icon"
541
+ svgProps={{ color: CAP_RED01 }}
542
+ ></CapIcon>
543
+ </CapColumn>
544
+ </>
545
+ )}
546
+ {!errorText && (
547
+ <>
548
+ <CapColumn
549
+ span={3}
550
+ className="validation-count-container"
551
+ >
552
+ <CapLabel
553
+ type="label9"
554
+ className="validation-count-label"
555
+ >
556
+ {formatMessage(messages.passedVal, {
557
+ number:
558
+ fileResult.data.length - 1 - totalFailCount,
559
+ })}
560
+ </CapLabel>
561
+ <CapIcon
562
+ type="check-circle"
563
+ className="validation-count-icon"
564
+ svgProps={{ color: CAP_PRIMARY.base }}
565
+ ></CapIcon>
566
+ </CapColumn>
567
+ <CapColumn
568
+ span={3}
569
+ className="validation-count-container"
570
+ >
571
+ <CapLabel
572
+ type="label9"
573
+ className="validation-count-label"
574
+ >
575
+ {formatMessage(messages.failedVal, {
576
+ number: totalFailCount,
577
+ })}
578
+ </CapLabel>
579
+ <CapIcon
580
+ type="warning"
581
+ className="validation-count-icon"
582
+ svgProps={{ color: CAP_RED01 }}
583
+ ></CapIcon>
584
+ </CapColumn>
585
+ <CapColumn span={4}>
586
+ {totalFailCount >= 1 && (
587
+ <a
588
+ onClick={downloadIssuesCSV}
589
+ className="download-issues-csv-link"
590
+ >
591
+ {formatMessage(messages.downloadIssues)}
592
+ </a>
593
+ )}
594
+ </CapColumn>
595
+ </>
596
+ )}
597
+ <CapColumn span={1} className="delete-csv">
598
+ <CapIcon
599
+ type="delete"
600
+ size="s"
601
+ onClick={deleteCSV}
602
+ ></CapIcon>
603
+ </CapColumn>
604
+ </CapRow>
605
+ <CapDivider
606
+ type="horizontal"
607
+ className="upload-file-preview-divider-2"
608
+ />
609
+ {errorText && <CapError>{errorText}</CapError>}
610
+ </div>
611
+ </CapRow>
612
+ </>
613
+ )}
614
+ </CapColumn>
615
+ <CapColumn span={8} className="upload-instructions-container">
616
+ <CapHeading type="h4">
617
+ {formatMessage(messages.uploadInstructions)}
618
+ </CapHeading>
619
+ <div className="upload-instruction-list">
620
+ <InstructionComp
621
+ number={1}
622
+ message={
623
+ <FormattedMessage
624
+ {...messages.downloadInstruction}
625
+ values={{
626
+ link: (
627
+ <a
628
+ onClick={downloadSampleCSV}
629
+ className="download-sample-link"
630
+ >
631
+ {formatMessage(messages.sampleCsv)}
632
+ </a>
633
+ ),
634
+ }}
635
+ />
636
+ }
637
+ />
638
+ <InstructionComp
639
+ number={2}
640
+ message={formatMessage(messages.firstRowInstruction)}
641
+ />
642
+ <InstructionComp
643
+ number={3}
644
+ message={formatMessage(messages.secondRowInstruction)}
645
+ />
646
+ <InstructionComp
647
+ number={4}
648
+ message={formatMessage(messages.duplicateEntriesInstruction)}
649
+ />
650
+ </div>
651
+ </CapColumn>
652
+ <SMSTraiFooter>
653
+ <CapButton
654
+ className="done-button"
655
+ onClick={onDoneCallback}
656
+ disabled={isDisableDone()}
657
+ >
658
+ <FormattedMessage {...messages.doneButtonLabel} />
659
+ </CapButton>
660
+ </SMSTraiFooter>
661
+ </CapRow>
662
+ </>
663
+ );
664
+ };
665
+
666
+ SmsTraiCreate.propTypes = {
667
+ dispatch: PropTypes.func.isRequired,
668
+ };
669
+
670
+ const mapStateToProps = createStructuredSelector({});
671
+
672
+ const mapDispatchToProps = (dispatch) => ({
673
+ actions: bindActionCreators(smsTraiActions, dispatch),
674
+ });
675
+
676
+ export default withCreatives({
677
+ WrappedComponent: injectIntl(SmsTraiCreate),
678
+ mapStateToProps,
679
+ mapDispatchToProps,
680
+ userAuth: true,
681
+ });