@capillarytech/creatives-library 7.9.14 → 7.9.16

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