@tiledesk/tiledesk-server 2.4.52 → 2.4.54

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,157 @@
1
+ var express = require('express');
2
+ var router = express.Router();
3
+ var winston = require('../config/winston');
4
+ var Segment = require("../models/segment");
5
+
6
+
7
+ router.post('/', function (req, res) {
8
+
9
+ winston.debug(req.body);
10
+ winston.debug("req.user", req.user);
11
+
12
+ var newSegment = new Segment({
13
+ name: req.body.name,
14
+ match: req.body.match,
15
+ filters: req.body.filters,
16
+ id_project: req.projectid,
17
+ createdBy: req.user.id
18
+ });
19
+
20
+ newSegment.save(function(err, segment) {
21
+ if (err) {
22
+ winston.error('Error saving the segment '+ JSON.stringify(segment), err);
23
+ return res.status(500).send({ success: false, msg: 'Error updating object.' });
24
+
25
+ }
26
+ winston.verbose('segment created ', segment.toJSON());
27
+
28
+
29
+ res.json(segment);
30
+ })
31
+
32
+ });
33
+
34
+ router.put('/:segmentid', function (req, res) {
35
+ winston.debug(req.body);
36
+ var update = {};
37
+
38
+ if (req.body.name!=undefined) {
39
+ update.name = req.body.name;
40
+ }
41
+
42
+ if (req.body.match!=undefined) {
43
+ update.match = req.body.match;
44
+ }
45
+ if (req.body.filters!=undefined) {
46
+ update.filters = req.body.filters;
47
+ }
48
+
49
+
50
+
51
+ Segment.findByIdAndUpdate(req.params.segmentid, update, { new: true, upsert: true }, function (err, updatedSegment) {
52
+ if (err) {
53
+ winston.error('--- > ERROR ', err);
54
+ return res.status(500).send({ success: false, msg: 'Error updating object.' });
55
+ }
56
+
57
+ res.json(updatedSegment);
58
+ });
59
+ });
60
+
61
+
62
+
63
+ router.delete('/:segmentid', function (req, res) {
64
+ winston.debug(req.body);
65
+
66
+ Segment.findByIdAndUpdate(req.params.segmentid, {status:0}, { new: true, upsert: true }, function (err, updatedSegment) {
67
+ if (err) {
68
+ winston.error('--- > ERROR ', err);
69
+ return res.status(500).send({ success: false, msg: 'Error updating object.' });
70
+ }
71
+
72
+ res.json(updatedSegment);
73
+ });
74
+ });
75
+
76
+
77
+ router.get('/:segmentid', function (req, res) {
78
+ winston.debug(req.body);
79
+
80
+ Segment.findById(req.params.segmentid, function (err, segment) {
81
+ if (err) {
82
+ return res.status(500).send({ success: false, msg: 'Error getting object.' });
83
+ }
84
+ if (!segment) {
85
+ return res.status(404).send({ success: false, msg: 'Object not found.' });
86
+ }
87
+ res.json(segment);
88
+ });
89
+ });
90
+
91
+
92
+ router.get('/', function (req, res) {
93
+
94
+ var limit = 40; // Number of request per page
95
+
96
+ if (req.query.limit) {
97
+ limit = parseInt(req.query.limit);
98
+ winston.debug('LEAD ROUTE - limit: '+limit);
99
+ }
100
+
101
+ var page = 0;
102
+
103
+ if (req.query.page) {
104
+ page = req.query.page;
105
+ }
106
+
107
+ var skip = page * limit;
108
+ winston.debug('LEAD ROUTE - SKIP PAGE ', skip);
109
+
110
+
111
+ var query = { "id_project": req.projectid, "status": 100};
112
+
113
+ var direction = -1; //-1 descending , 1 ascending
114
+ if (req.query.direction) {
115
+ direction = req.query.direction;
116
+ }
117
+
118
+ var sortField = "createdAt";
119
+ if (req.query.sort) {
120
+ sortField = req.query.sort;
121
+ }
122
+
123
+ var sortQuery = {};
124
+ sortQuery[sortField] = direction;
125
+
126
+ winston.debug("sort query", sortQuery);
127
+
128
+
129
+
130
+ return Segment.find(query).
131
+ skip(skip).limit(limit).
132
+ sort(sortQuery).
133
+ exec(function (err, segments) {
134
+ if (err) {
135
+ winston.error('segments ROUTE - REQUEST FIND ERR ', err)
136
+ return (err);
137
+ }
138
+
139
+ // blocked to 1000 TODO increases it
140
+ // collection.count is deprecated, and will be removed in a future version. Use Collection.countDocuments or Collection.estimatedDocumentCount instead
141
+ return Segment.countDocuments(query, function (err, totalRowCount) {
142
+
143
+ var objectToReturn = {
144
+ perPage: limit,
145
+ count: totalRowCount,
146
+ segments: segments
147
+ };
148
+
149
+ return res.json(objectToReturn);
150
+ });
151
+ });
152
+ });
153
+
154
+
155
+
156
+
157
+ module.exports = router;
package/routes/users.js CHANGED
@@ -185,5 +185,29 @@ router.get('/', function (req, res) {
185
185
  });
186
186
  });
187
187
 
188
+ router.post('/loginemail', function (req, res) {
189
+
190
+ winston.debug("/loginemail... req.body: ", req.body);
191
+ let user_id = req.user._id;
192
+ let token = req.headers.authorization;
193
+
194
+ let project_id = req.body.id_project;
195
+ if (!project_id) {
196
+ res.status(500).send({ success: false, error: "missing 'id_project' field" })
197
+ }
198
+
199
+ User.findById(user_id, (err, user) => {
200
+ if (err) {
201
+ return res.status(404).send({ success: false, message: "No user found" });
202
+ }
203
+ winston.debug("user found: ", user);
204
+
205
+ emailService.sendEmailRedirectOnDesktop(user.email, token, project_id, user.email)
206
+ return res.status(200).send({ success: true, message: "Sending email..."})
207
+ })
208
+
209
+
210
+ })
211
+
188
212
 
189
213
  module.exports = router;
@@ -1851,6 +1851,39 @@ async sendRequestTranscript(to, messages, request, project) {
1851
1851
 
1852
1852
  }
1853
1853
 
1854
+ async sendEmailRedirectOnDesktop(to, token, project_id) {
1855
+ winston.debug("sendEmailRedirectOnDesktop: " + to);
1856
+
1857
+ var that = this;
1858
+
1859
+ let html = await this.readTemplate('redirectToDesktopEmail.html');
1860
+
1861
+ let envTemplate = process.env.EMAIL_REDIRECT_TO_DESKTOP_TEMPLATE
1862
+ winston.debug("envTemplate: " + envTemplate);
1863
+
1864
+ if (envTemplate) {
1865
+ html = envTemplate;
1866
+ }
1867
+
1868
+ winston.debug("html: " + html);
1869
+
1870
+ let template = handlebars.compile(html);
1871
+
1872
+ let baseScope = JSON.parse(JSON.stringify(that));
1873
+ delete baseScope.pass;
1874
+
1875
+ let replacements = {
1876
+ baseScope: baseScope,
1877
+ token: token,
1878
+ project_id: project_id
1879
+ }
1880
+
1881
+ html = template(replacements);
1882
+
1883
+ that.send({ to: to, subject: "Join Tiledesk from Desktop", html: html });
1884
+
1885
+ }
1886
+
1854
1887
  parseText(text, payload) {
1855
1888
 
1856
1889
 
@@ -0,0 +1,110 @@
1
+ var winston = require('../config/winston');
2
+ const axios = require("axios").default;
3
+ var configGlobal = require('../config/global');
4
+ require('dotenv').config();
5
+
6
+ let openai_endpoint = process.env.OPENAI_ENDPOINT;
7
+ let kb_endpoint = process.env.KB_ENDPOINT;
8
+
9
+ class OpenaiService {
10
+
11
+ // OPEN AI
12
+ completions(data, gptkey) {
13
+
14
+ winston.debug("[OPENAI SERVICE] openai endpoint: ", openai_endpoint);
15
+
16
+ return new Promise((resolve, reject) => {
17
+
18
+ axios({
19
+ url: openai_endpoint + "/chat/completions",
20
+ headers: {
21
+ 'Content-Type': 'application/json',
22
+ 'Authorization': "Bearer " + gptkey
23
+ },
24
+ data: data,
25
+ method: 'POST'
26
+ }).then((resbody) => {
27
+ //winston.debug("[Openai] completions resbody: ", resbody.data);
28
+ resolve(resbody);
29
+ }).catch((err) => {
30
+ console.log("err: ", err);
31
+ // winston.error("[Openai] completions error: ", err);
32
+ reject(err);
33
+ })
34
+
35
+ })
36
+
37
+ }
38
+
39
+
40
+ // PUGLIA AI
41
+ checkStatus(data) {
42
+ winston.debug("[OPENAI SERVICE] kb endpoint: ", kb_endpoint);
43
+
44
+ return new Promise((resolve, reject) => {
45
+
46
+ axios({
47
+ url: kb_endpoint + "/scrape/status",
48
+ headers: {
49
+ 'Content-Type': 'application/json'
50
+ },
51
+ data: data,
52
+ method: 'POST'
53
+ }).then((resbody) => {
54
+ resolve(resbody);
55
+ }).catch((err) => {
56
+ console.log("err: ", err);
57
+ reject(err);
58
+ })
59
+
60
+ })
61
+ }
62
+
63
+ startScrape(data) {
64
+ winston.debug("[OPENAI SERVICE] kb endpoint: ", kb_endpoint);
65
+
66
+ return new Promise((resolve, reject) => {
67
+
68
+ axios({
69
+ url: kb_endpoint + "/scrape/",
70
+ headers: {
71
+ 'Content-Type': 'application/json'
72
+ },
73
+ data: data,
74
+ method: 'POST'
75
+ }).then((resbody) => {
76
+ resolve(resbody);
77
+ }).catch((err) => {
78
+ console.log("err: ", err);
79
+ reject(err);
80
+ })
81
+
82
+ })
83
+ }
84
+
85
+ ask(data) {
86
+ winston.debug("[OPENAI SERVICE] kb endpoint: ", kb_endpoint);
87
+
88
+ return new Promise((resolve, reject) => {
89
+
90
+ axios({
91
+ url: kb_endpoint + "/qa/",
92
+ headers: {
93
+ 'Content-Type': 'application/json'
94
+ },
95
+ data: data,
96
+ method: 'POST'
97
+ }).then((resbody) => {
98
+ resolve(resbody);
99
+ }).catch((err) => {
100
+ console.log("err: ", err);
101
+ reject(err);
102
+ })
103
+
104
+ })
105
+ }
106
+ }
107
+
108
+ var openaiService = new OpenaiService();
109
+
110
+ module.exports = openaiService;
@@ -0,0 +1,304 @@
1
+ <!DOCTYPE html
2
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+
4
+ <html lang="en" xmlns="http://www.w3.org/1999/xhtml"
5
+ style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
6
+
7
+ <head>
8
+ <meta name="viewport" content="width=device-width" />
9
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
10
+ <title>Join Tiledesk from Desktop</title>
11
+
12
+ <style type="text/css">
13
+ img {
14
+ max-width: 100%;
15
+ text-align: center !important;
16
+ }
17
+
18
+ img.CToWUd {
19
+ margin-bottom: 16px;
20
+ max-width: 200px !important;
21
+ width: 200px !important;
22
+ min-width: 200px !important;
23
+ outline: none;
24
+ text-decoration: none;
25
+ border: none;
26
+ height: auto;
27
+ margin-left: 0px;
28
+ }
29
+
30
+ body {
31
+ -webkit-font-smoothing: antialiased;
32
+ -webkit-text-size-adjust: none;
33
+ width: 100% !important;
34
+ height: 100%;
35
+ line-height: 1.6em;
36
+ }
37
+
38
+ body {
39
+ background-color: #f6f6f6;
40
+ }
41
+
42
+ .header-image-container {
43
+ margin-top: 10px;
44
+ height: 40px;
45
+ background-image: url("https://tiledesk.com/wp-content/uploads/2022/08/tiledesk_v1-1.png");
46
+ background-size: contain;
47
+ background-position: center;
48
+ background-repeat: no-repeat;
49
+ }
50
+
51
+ @media only screen and (max-width: 640px) {
52
+ body {
53
+ padding: 0 !important;
54
+ }
55
+
56
+ h1 {
57
+ font-weight: 800 !important;
58
+ margin: 20px 0 5px !important;
59
+ text-align: center !important;
60
+ }
61
+
62
+ h2 {
63
+ font-weight: 800 !important;
64
+ margin: 20px 0 5px !important;
65
+ }
66
+
67
+ h3 {
68
+ font-weight: 800 !important;
69
+ margin: 20px 0 5px !important;
70
+ }
71
+
72
+ h4 {
73
+ font-weight: 800 !important;
74
+ margin: 20px 0 5px !important;
75
+ }
76
+
77
+ h1 {
78
+ font-size: 22px !important;
79
+ }
80
+
81
+ h2 {
82
+ font-size: 18px !important;
83
+ }
84
+
85
+ h3 {
86
+ font-size: 16px !important;
87
+ }
88
+
89
+ .container {
90
+ padding: 0 !important;
91
+ width: 100% !important;
92
+ }
93
+
94
+ .content {
95
+ padding: 0 !important;
96
+ }
97
+
98
+ .content-wrap {
99
+ padding: 10px !important;
100
+ }
101
+
102
+ .invoice {
103
+ width: 100% !important;
104
+ }
105
+ }
106
+ </style>
107
+ </head>
108
+
109
+ <body itemscope itemtype="http://schema.org/EmailMessage"
110
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;"
111
+ bgcolor="#f6f6f6">
112
+
113
+ <table class="body-wrap"
114
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;"
115
+ bgcolor="#f6f6f6">
116
+ <tr
117
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
118
+ <td
119
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;"
120
+ valign="top"></td>
121
+ <td class="container" width="600"
122
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;"
123
+ valign="top">
124
+ <div class="content"
125
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;">
126
+
127
+ <table class="main" width="100%" cellpadding="0" cellspacing="0"
128
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;"
129
+ bgcolor="#fff">
130
+
131
+
132
+ <tr
133
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
134
+
135
+ <td class="alert alert-warning"
136
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; margin: 0;"
137
+ align="center" valign="top">
138
+ <div>
139
+ <div class="header-image-container"></div>
140
+ <h2 style="margin-top: 40px;">Get Ready for a Full Tiledesk Experience!</h2>
141
+ </div>
142
+
143
+ </td>
144
+ </tr>
145
+
146
+ <tr
147
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
148
+ <td class="content-wrap"
149
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;"
150
+ valign="top">
151
+ <table width="100%" cellpadding="0" cellspacing="0"
152
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
153
+
154
+
155
+ <tr
156
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
157
+ <td class="content-block">
158
+ <div style="text-align: center;">
159
+ For the optimal Tiledesk experience, we recommend using it on a desktop device. We've made
160
+ access easier for you with a personalized link.
161
+
162
+ <div style="margin-top: 20px; font-weight: 600;">Access Tiledesk on your desktop now 👇</div>
163
+
164
+ <div style="margin-top: 10px;">
165
+ <a href="{{baseScope.baseUrl}}/#/project/{{project_id}}/home?token={{token}}"
166
+ style=" background-color: #ff8574 !important; border: none; color: white; padding: 6px 18px; text-align: center; text-decoration: none; display: inline-block; font-size: 14px; font-weight: 600; letter-spacing: 1px; margin: 4px 2px; cursor: pointer; border-radius: 8px;">
167
+ Enjoy Tiledesk
168
+ </a>
169
+ </div>
170
+
171
+ </div>
172
+
173
+ <div style="margin-top: 40px;">
174
+ Alternatively, simply copy and paste this URL into your browser:
175
+ <div>
176
+ <a
177
+ href="{{baseScope.baseUrl}}/#/project/{{project_id}}/home?token={{token}}">
178
+ {{baseScope.baseUrl}}/#/project/{{project_id}}/home
179
+ </a>
180
+ </div>
181
+ </div>
182
+
183
+ </td>
184
+ </tr>
185
+
186
+ <tr
187
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
188
+ <td class="content-block"
189
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;"
190
+ valign="top">
191
+ </td>
192
+ </tr>
193
+ </table>
194
+ </td>
195
+ </tr>
196
+
197
+ <tr>
198
+ <td>
199
+ <hr style="width:94%;height:1px;border:none;background-color: #cacaca;">
200
+
201
+ <div style="display: flex; padding: 20px 18px; color: #888888; align-items: center;">
202
+ <span>Powered by </span>
203
+ <span style="display: flex;"><img
204
+ src="https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png" width="15"
205
+ height="15" style="margin-left: 6px; margin-top: 2px;" /></span>
206
+ <span style="font-weight: bold; margin-left: 2px;">Tiledesk</span>
207
+ </div>
208
+
209
+ </td>
210
+ </tr>
211
+
212
+
213
+ </table>
214
+ <div class="footer"
215
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;">
216
+ <table width="100%"
217
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
218
+
219
+ <!-- <tr
220
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
221
+ <td class="aligncenter content-block"
222
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0;"
223
+ align="center" valign="top">
224
+
225
+ <div style="margin-bottom: 10px; font-style: italic;">Follow us on:</div>
226
+ <div style="display: flex; flex-direction: row;">
227
+
228
+ <a href="https://www.facebook.com/tiledesk" target="_blank">
229
+ <div
230
+ style="border-color: #3d6ad6; border: solid 1px; border-radius: 14px; padding: 4px; margin: 0px 6px; cursor: pointer;">
231
+ <img
232
+ src="https://github.com/Tiledesk/tiledesk-dashboard/assets/45603238/f762a3d7-541a-4b78-ae33-c35cdbe3b083"
233
+ width="20px" height="20px" />
234
+ </div>
235
+ </a>
236
+
237
+ <a href="https://www.youtube.com/@tiledesk" target="_blank">
238
+ <div
239
+ style="border-color: #e7332f; border: solid 1px; border-radius: 14px; padding: 4px; margin: 0px 6px; cursor: pointer;">
240
+ <img
241
+ src="https://github.com/Tiledesk/tiledesk-dashboard/assets/45603238/5dc5be2d-8bd3-43cf-b1ca-f12a36c01f1a"
242
+ width="20px" height="20px" />
243
+ </div>
244
+ </a>
245
+
246
+ <a href="https://www.linkedin.com/company/tiledesk/" target="_blank">
247
+ <div
248
+ style="border-color: #1f77b5; border: solid 1px; border-radius: 14px; padding: 4px; margin: 0px 6px; cursor: pointer;">
249
+ <img
250
+ src="https://github.com/Tiledesk/tiledesk-dashboard/assets/45603238/52d8909f-c847-4b44-8dfb-7cd041e481c3"
251
+ width="20px" height="20px" />
252
+ </div>
253
+ </a>
254
+
255
+ <a href="https://www.instagram.com/tiledesk/" target="_blank">
256
+ <div
257
+ style="border-color: #f78881; border: solid 1px; border-radius: 14px; padding: 4px; margin: 0px 6px; cursor: pointer;">
258
+ <img
259
+ src="https://github.com/Tiledesk/tiledesk-dashboard/assets/45603238/4c35afe2-277a-4fdd-8e50-0844148216c8"
260
+ width="20px" height="20px" />
261
+ </div>
262
+ </a>
263
+
264
+ <a href="https://twitter.com/tiledesk" target="_blank">
265
+ <div
266
+ style="border-color: #2ea1f2; border: solid 1px; border-radius: 14px; padding: 4px; margin: 0px 6px; cursor: pointer;">
267
+ <img
268
+ src="https://github.com/Tiledesk/tiledesk-dashboard/assets/45603238/3288635e-50b6-4b2d-bccc-0c9313dd11a5"
269
+ width="20px" height="20px" />
270
+ </div>
271
+ </a>
272
+
273
+ </div>
274
+
275
+ </td>
276
+ </tr> -->
277
+
278
+ <tr
279
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;">
280
+ <td class="aligncenter content-block"
281
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0;"
282
+ align="center" valign="top">
283
+ <span><a href="http://www.tiledesk.com"
284
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">
285
+ Tiledesk.com </a></span>
286
+ <br><span><a href="%unsubscribe_url%"
287
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;">Unsubscribe</a></span>
288
+ </td>
289
+ </tr>
290
+ </table>
291
+
292
+
293
+ </div>
294
+ </div>
295
+ </td>
296
+ <td
297
+ style="font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;"
298
+ valign="top"></td>
299
+ </tr>
300
+ </table>
301
+
302
+ </body>
303
+
304
+ </html>