@subiz/widget-v4 4.1.126 → 4.1.129

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 (62) hide show
  1. package/assets/less/avatar.less +1 -1
  2. package/assets/less/chatbox.less +25 -2
  3. package/assets/less/form.less +77 -2
  4. package/assets/less/message.less +4 -0
  5. package/assets/less/widget.less +20 -0
  6. package/assets/less/widget_header.less +0 -2
  7. package/compiled_src/chatbox/ChatButton.js +10 -4
  8. package/compiled_src/chatbox/ConnectionStatus.js +58 -0
  9. package/compiled_src/chatbox/Event.js +10 -8
  10. package/compiled_src/chatbox/FormAttachment2.js +80 -0
  11. package/compiled_src/chatbox/Widget.js +5 -0
  12. package/compiled_src/form/Form.js +274 -0
  13. package/compiled_src/form/FormPage.js +131 -0
  14. package/compiled_src/form/index.js +72 -0
  15. package/compiled_src/store/api.js +3 -0
  16. package/compiled_src/store/realtime_fake.js +4 -0
  17. package/compiled_src/store/store.js +25 -0
  18. package/compiled_src/util/campaign.js +3 -5
  19. package/compiled_src/util/common.js +2 -2
  20. package/dist/{1964f21ffc8.127.js → 1967c022bc1.127.js} +1 -1
  21. package/dist/{1964f21ffc8.132.js → 1967c022bc1.132.js} +1 -1
  22. package/dist/{1964f21ffc8.185.js → 1967c022bc1.185.js} +1 -1
  23. package/dist/1967c022bc1.278.js +1 -0
  24. package/dist/{1964f21ffc8.490.js → 1967c022bc1.490.js} +1 -1
  25. package/dist/{1964f21ffc8.74.js → 1967c022bc1.74.js} +1 -1
  26. package/dist/1967c022bc1.app.js +1 -0
  27. package/dist/1967c022bc1.form.html +1 -0
  28. package/dist/1967c022bc1.form.js +1 -0
  29. package/dist/{1964f21ffc8.index.html → 1967c022bc1.index.html} +1 -1
  30. package/dist/{1964f21ffc8.library.html → 1967c022bc1.library.html} +1 -1
  31. package/dist/1967c022bc1.library.js +1 -0
  32. package/dist/{1964f21ffc8.preview.html → 1967c022bc1.preview.html} +1 -1
  33. package/dist/1967c022bc1.preview.js +1 -0
  34. package/dist/1967c022bc1.webview.js +1 -0
  35. package/dist/278.js +1 -1
  36. package/dist/HASH +1 -1
  37. package/dist/screenshoot.js +1198 -1001
  38. package/form.html +10 -0
  39. package/package.json +5 -4
  40. package/src/chatbox/ChatButton.js +10 -8
  41. package/src/chatbox/ConnectionStatus.js +42 -0
  42. package/src/chatbox/Event.js +10 -10
  43. package/src/chatbox/FormAttachment2.js +58 -0
  44. package/src/chatbox/Widget.js +2 -0
  45. package/src/form/Form.js +293 -0
  46. package/src/form/FormPage.js +95 -0
  47. package/src/form/index.js +79 -0
  48. package/src/store/api.js +8 -0
  49. package/src/store/realtime_fake.js +8 -4
  50. package/src/store/store.js +29 -0
  51. package/src/util/campaign.js +3 -5
  52. package/src/util/common.js +2 -2
  53. package/webpack.dev.js +2 -0
  54. package/webpack.prod.js +7 -0
  55. package/dist/1964f21ffc8.278.js +0 -1
  56. package/dist/1964f21ffc8.app.js +0 -1
  57. package/dist/1964f21ffc8.library.js +0 -1
  58. package/dist/1964f21ffc8.preview.js +0 -1
  59. package/dist/1964f21ffc8.webview.js +0 -1
  60. /package/dist/{1964f21ffc8.27.js → 1967c022bc1.27.js} +0 -0
  61. /package/dist/{1964f21ffc8.459.js → 1967c022bc1.459.js} +0 -0
  62. /package/dist/{1964f21ffc8.997.js → 1967c022bc1.997.js} +0 -0
package/form.html ADDED
@@ -0,0 +1,10 @@
1
+ <html>
2
+ <head>
3
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
4
+ </head>
5
+ <body style="margin: 0;">
6
+ <div
7
+ id="form"
8
+ ></div>
9
+ </body>
10
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@subiz/widget-v4",
3
- "version": "4.1.126",
3
+ "version": "4.1.129",
4
4
  "main": "./compiled_src/lib.js",
5
5
  "scripts": {
6
6
  "dev": "npx webpack serve --config webpack.dev.js",
@@ -11,7 +11,7 @@
11
11
  "dependencies": {
12
12
  "@subiz/ajax": "^3.0.19",
13
13
  "@subiz/flow": "^1.1.0",
14
- "@subiz/wsclient": "^4.0.28",
14
+ "@subiz/wsclient": "^4.0.31",
15
15
  "cookie_js": "1.4.2",
16
16
  "core-js": "^3.41.0",
17
17
  "css-loader": "5.2.7",
@@ -29,8 +29,9 @@
29
29
  "babel-loader": "^10.0.0",
30
30
  "babel-plugin-syntax-jsx": "^6.18.0",
31
31
  "html-webpack-plugin": "^5.6.3",
32
- "puppeteer": "^24.6.1",
33
- "webpack": "^5.99.6",
32
+ "prettier": "^3.5.3",
33
+ "puppeteer": "^24.7.2",
34
+ "webpack": "^5.99.7",
34
35
  "webpack-bundle-analyzer": "^4.10.2",
35
36
  "webpack-cli": "^6.0.1",
36
37
  "webpack-dev-server": "^5.2.1"
@@ -31,14 +31,16 @@ export default class ChatButton extends Com {
31
31
  isquickreplies = true
32
32
  }
33
33
  })
34
- if (isquickreplies) return
34
+ let format = sb.get(ev, 'data.message.format')
35
+ let block = sb.get(ev, 'data.message.block')
36
+ if (block || format == 'block') {
37
+ if (sb.isBlockEmpty(block) && isquickreplies) return
38
+ } else {
39
+ let message = sb.get(ev, 'data.message.text') || ''
40
+ if (!message && isquickreplies) return
41
+ }
35
42
 
36
- // only set preview have content ev
37
- var tags = sb.filter(
38
- sb.map(this.renderMessagePreview(ev), (tag) => sb.trim(tag)),
39
- (a) => a,
40
- )
41
- if (sb.size(tags) > 0) this.setState({displayPreview: true, previewEvent: ev})
43
+ this.setState({displayPreview: true, previewEvent: ev})
42
44
  })
43
45
  }
44
46
 
@@ -386,7 +388,7 @@ export default class ChatButton extends Com {
386
388
 
387
389
  renderMessagePreview(ev, defmsg) {
388
390
  defmsg = defmsg || this.props.t('sent_you_a_message')
389
- var format = sb.get(ev, 'data.message.format')
391
+ let format = sb.get(ev, 'data.message.format')
390
392
  let block = sb.get(ev, 'data.message.block')
391
393
  if (block || format == 'block') {
392
394
  if (sb.isBlockEmpty(block)) {
@@ -0,0 +1,42 @@
1
+ import sb from '../util/index.js'
2
+ import {Com} from '../common.js'
3
+ import {h, createRef} from 'preact'
4
+
5
+ export default class ConnectionStatus extends Com {
6
+ constructor(props) {
7
+ super(props)
8
+ this.props.store.onConnection(this, this.onChangeConnectionStatus)
9
+ this.state = {status: this.props.store.getConnectionStatus()}
10
+ }
11
+
12
+ onChangeConnectionStatus = () => {
13
+ let newstatus = this.props.store.getConnectionStatus()
14
+ let currentstatus = this.state.status
15
+ this.setState({status: newstatus})
16
+ if (currentstatus !== 'active' && newstatus === 'active') this.changeStatusFailedToSuccess()
17
+ }
18
+
19
+ changeStatusFailedToSuccess = () => {
20
+ this.setState({status: 'just_active'})
21
+ if (this.props.cid) this.props.store.fetchMoreEvents(this.props.cid)
22
+ if (this._timeout) clearTimeout(this._timeout)
23
+ this._timeout = setTimeout(() => {
24
+ this.setState({status: 'active'})
25
+ }, 2000)
26
+ }
27
+
28
+ render() {
29
+ if (this.state.status === 'active') return
30
+
31
+ let cls = 'widget-internet-status'
32
+ let text = ''
33
+ if (this.state.status !== 'just_active') {
34
+ cls += ' negative'
35
+ text = `${this.props.t('network_error')}. ${this.props.t('try_reconnecting')}...`
36
+ } else {
37
+ cls += ' positive'
38
+ text = this.props.t('reconnect_successfully')
39
+ }
40
+ return <div class={cls}>{text}</div>
41
+ }
42
+ }
@@ -3,7 +3,7 @@ import AudioAttachments from './AudioAttachments.js'
3
3
  import FileAttachments from './FileAttachments.js'
4
4
  import ButtonAttachments from './ButtonAttachments.js'
5
5
  import QuickReplies from './QuickReplies.js'
6
- import FormAttachments from './FormAttachments.js'
6
+ import FormAttachment from './FormAttachment2.js'
7
7
  import FormSubmitAttachments from './FormSubmitAttachments.js'
8
8
  import GenericTemplateAttachments from './GenericTemplateAttachments.js'
9
9
  import ProductAttachments from './ProductAttachments.js'
@@ -232,6 +232,9 @@ export default class Event extends Com {
232
232
 
233
233
  renderMessageSent() {
234
234
  let ev = this.props.event.ev
235
+ let attachments = sb.get(ev, 'data.message.attachments')
236
+ let hasForm = sb.find(attachments, att => att.type === 'form')
237
+ if (ev.id === 'evshpgaeetsplfqprzmynrvrl') hasForm = true
235
238
  this.tryLoadOrder(ev)
236
239
  let t = new sb.Date(ev.created).toLocaleString()
237
240
  // message of agent
@@ -240,10 +243,13 @@ export default class Event extends Com {
240
243
  if (this.props.event.mode === 'lead') $sender = this.renderAgentAvatar(ev)
241
244
  let innercls = 'agent-convo-event-inner'
242
245
  if (this.props.isLast) innercls += ' last'
246
+
247
+ let wrappercls = 'agent-convo-event-wrapper'
248
+ if (hasForm) wrappercls += ' full'
243
249
  return (
244
250
  <div class='convo-event agent-message'>
245
251
  {$sender}
246
- <div class='agent-convo-event-wrapper'>
252
+ <div class={wrappercls}>
247
253
  <div class={innercls}>
248
254
  <TextMessage
249
255
  showTime={this.props.event.show_time}
@@ -257,13 +263,7 @@ export default class Event extends Com {
257
263
  <VideoAttachments store={this.props.store} ev={ev} />
258
264
  <FileAttachments store={this.props.store} ev={ev} />
259
265
  <ButtonAttachments store={this.props.store} ev={ev} t={this.props.t} />
260
- <FormAttachments
261
- store={this.props.store}
262
- onSubmit={this.props.onFormSubmit}
263
- ref={this.refForm}
264
- ev={ev}
265
- t={this.props.t}
266
- />
266
+ <FormAttachment store={this.props.store} ev={ev} t={this.props.t} />
267
267
  <FormSubmitAttachments store={this.props.store} ev={ev} t={this.props.t} />
268
268
  <GenericTemplateAttachments
269
269
  store={this.props.store}
@@ -326,7 +326,7 @@ export default class Event extends Com {
326
326
  <VideoAttachments store={this.props.store} ev={ev} />
327
327
  <FileAttachments store={this.props.store} ev={ev} />
328
328
  <ButtonAttachments store={this.props.store} ev={ev} t={this.props.t} />
329
- <FormAttachments store={this.props.store} onSubmit={this.props.onFormSubmit} ref={this.refForm} ev={ev} />
329
+ <FormAttachment store={this.props.store} t={this.props.t} ev={ev} />
330
330
  <ProductAttachments t={this.props.t} store={this.props.store} ev={ev} />
331
331
  <FormSubmitAttachments store={this.props.store} ev={ev} />
332
332
  </div>
@@ -0,0 +1,58 @@
1
+ import sb from '../util/index.js'
2
+
3
+ import {Com} from '../common.js'
4
+ import {h, createRef} from 'preact'
5
+ import Form from '../form/Form.js'
6
+
7
+ // ['form', 'ev'],
8
+ export default class FormAttachment extends Com {
9
+ constructor(props) {
10
+ super(props)
11
+ this.state = {
12
+ submission: {},
13
+ submiting: false,
14
+ errorMessage: '',
15
+ isSubmited: false,
16
+ }
17
+ }
18
+
19
+ onChangeFormValues = (values) => {
20
+ this.setState({submission: {values}})
21
+ }
22
+
23
+ onSubmit = async () => {
24
+ this.setState({errorMessage: ''})
25
+ if (this.state.submiting) return
26
+ this.setState({submiting: true})
27
+ let res = await this.props.store.submitForm2(this.props.token, this.state.submission)
28
+ if (res.error) {
29
+ this.setState({submiting: false, errorMessage: sb.get(res, 'error.code')})
30
+ return
31
+ }
32
+ this.setState({submiting: false, isSubmited: true})
33
+ }
34
+
35
+ render() {
36
+ let attachments = sb.get(this.props.ev, 'data.message.attachments') || []
37
+ let formAttachment = sb.find(attachments, (att) => att.type === 'form')
38
+ if (!formAttachment) return
39
+ const {submission, errorMessage, isSubmited, submiting} = this.state
40
+ let formSetting = formAttachment.form || {}
41
+ return (
42
+ <div onClick={(e) => e.stopPropagation()}>
43
+ <Form
44
+ store={this.props.store}
45
+ formSetting={formSetting}
46
+ mode='attachment'
47
+ t={this.props.t}
48
+ submission={submission}
49
+ errorMessage={errorMessage}
50
+ isSubmited={isSubmited}
51
+ submiting={submiting}
52
+ onChangeFormValues={this.onChangeFormValues}
53
+ onSubmit={this.onSubmit}
54
+ />
55
+ </div>
56
+ )
57
+ }
58
+ }
@@ -10,6 +10,7 @@ import MessageInput from './MessageInput.js'
10
10
  import Events from './Events.js'
11
11
  // import Typing from './Typing.js'
12
12
  import Rating from './Rating.js'
13
+ import ConnectionStatus from './ConnectionStatus.js'
13
14
  // import Order from './Order.js'
14
15
  let Order = false
15
16
  const LoadOrder = () => import(/*webpackChunkName: "Order" */ './Order.js')
@@ -848,6 +849,7 @@ export default class Widget extends Com {
848
849
  t={this.props.t}
849
850
  plugin={this.props.plugin}
850
851
  />
852
+ <ConnectionStatus cid={this.props.cid} store={this.props.store} t={this.props.t} />
851
853
  </div>
852
854
  <Rating
853
855
  forceView={this.props.view}
@@ -0,0 +1,293 @@
1
+ import {Com} from '../common.js'
2
+ import {h, createRef} from 'preact'
3
+ import sb from '../util/index.js'
4
+ import Block from '../chatbox/block.js'
5
+
6
+ export default class Form2 extends Com {
7
+ state = {
8
+ errorFields: {},
9
+ submiting: false,
10
+ }
11
+
12
+ constructor(props) {
13
+ super(props)
14
+
15
+ let components = sb.get(this.props.formSetting, 'components') || []
16
+ sb.each(components, (com) => {
17
+ if (com.type === 'input' && com.input_type !== 'checkbox' && com.input_type !== 'radio') {
18
+ this[`${com.key}_input`] = createRef()
19
+ }
20
+ })
21
+ }
22
+
23
+ renderFormComponent = (com, idx) => {
24
+ if (com.type === 'input') return this.renderField(com, idx)
25
+ if (com.type === 'button') return this.renderSubmitButton(com, idx)
26
+ if (this.props.isSubmited) return
27
+ return (
28
+ <div class='form-component form-component-block'>
29
+ <Block block={com} />
30
+ </div>
31
+ )
32
+ }
33
+
34
+ renderSubmitButton = (com, idx) => {
35
+ if (this.props.isSubmited) return
36
+ return (
37
+ <div class='form-component form-component-submit-button'>
38
+ <div class='form-control--valid-input--error'>{this.props.errorMessage}</div>
39
+ <div>
40
+ <button class='btn--start-convo' disabled={this.props.submiting}>
41
+ {com.label}
42
+ </button>
43
+ </div>
44
+ </div>
45
+ )
46
+ }
47
+
48
+ renderField = (com, idx) => {
49
+ let errorMsg = this.state.errorFields[com.key]
50
+ if (this.props.isSubmited) {
51
+ let val = sb.find(this.props.submission.values, (f) => f.key === com.key) || {}
52
+ val = val.value || '""'
53
+ val = sb.parseJSON(val)
54
+ if (Array.isArray(val)) val = val.join(', ')
55
+
56
+ return (
57
+ <div class='form-component submitted' key={com.key}>
58
+ <label class='label'>{com.label}</label>
59
+ <div>{val || <span class='text-muted text-italic'>{this.props.t('no_info_input')}</span>}</div>
60
+ </div>
61
+ )
62
+ }
63
+ return (
64
+ <div class='form-component' key={com.key}>
65
+ <label class='label'>
66
+ {com.label} {com.is_required && <span class='red'>*</span>}
67
+ </label>
68
+ {com.input_type === 'text' && this.renderFieldTextInput(com, idx)}
69
+ {com.input_type === 'multiple' && this.renderFieldTextareaInput(com, idx)}
70
+ {com.input_type === 'date' && this.renderFieldDateInput(com, idx)}
71
+ {com.input_type === 'radio' && this.renderFieldRadioInput(com, idx)}
72
+ {com.input_type === 'checkbox' && this.renderFieldCheckboxInput(com, idx)}
73
+ {com.input_type === 'dropdown' && this.renderFieldDropdownInput(com, idx)}
74
+ {errorMsg && <div class='form-control--valid-input--error'>{errorMsg}</div>}
75
+ </div>
76
+ )
77
+ }
78
+
79
+ renderFieldTextInput = (com, idx) => {
80
+ let cls = 'form-control'
81
+ if (this.state.errorFields[com.key]) cls = 'form-control input--error'
82
+ let val = sb.find(this.props.submission.values, (f) => f.key === com.key) || {}
83
+ val = val.value || '""'
84
+ val = sb.parseJSON(val)
85
+ return (
86
+ <input
87
+ ref={this[`${com.key}_input`]}
88
+ type='text'
89
+ class={cls}
90
+ placeholder={com.placeholder}
91
+ value={val}
92
+ onInput={(e) => {
93
+ this.onChangeValue(com.key, e.target.value)
94
+ this.clearError(com.key)
95
+ }}
96
+ />
97
+ )
98
+ }
99
+
100
+ renderFieldDateInput = (com, idx) => {
101
+ let val = sb.find(this.props.submission.values, (f) => f.key === com.key) || {}
102
+ val = val.value || '""'
103
+ val = sb.parseJSON(val)
104
+ return (
105
+ <input
106
+ ref={this[`${com.key}_input`]}
107
+ class='form-control'
108
+ type='date'
109
+ value={val}
110
+ onInput={(e) => {
111
+ this.onChangeValue(com.key, e.target.value)
112
+ this.clearError(com.key)
113
+ }}
114
+ />
115
+ )
116
+ }
117
+
118
+ renderFieldRadioInput = (com, idx) => {
119
+ let val = sb.find(this.props.submission.values, (f) => f.key === com.key) || {}
120
+ val = val.value || '""'
121
+ val = sb.parseJSON(val)
122
+ return sb.map(com.input_options, (option, oidx) => {
123
+ let id = com.key + '_' + oidx
124
+ return (
125
+ <div class='radio'>
126
+ <input
127
+ type='radio'
128
+ name={com.key}
129
+ id={id}
130
+ checked={option.value === val}
131
+ onChange={() => this.toggleRadioField(com.key, option.value)}
132
+ />
133
+ <label class='radio--label' for={id}>
134
+ {option.label}
135
+ </label>
136
+ <div class='radio--check' />
137
+ </div>
138
+ )
139
+ })
140
+ }
141
+
142
+ renderFieldCheckboxInput = (com, idx) => {
143
+ let val = sb.find(this.props.submission.values, (f) => f.key === com.key) || {}
144
+ val = val.value || '[]'
145
+ val = sb.parseJSON(val)
146
+ return sb.map(com.input_options, (option, oidx) => {
147
+ let id = com.key + '_' + oidx
148
+ return (
149
+ <div class='checkbox'>
150
+ <input
151
+ type='checkbox'
152
+ name={com.key}
153
+ id={id}
154
+ checked={val.includes(option.value)}
155
+ onChange={() => this.toggleCheckboxField(com.key, option.value)}
156
+ />
157
+ <label class='checkbox--label' for={id}>
158
+ {option.label}
159
+ </label>
160
+ <div class='checkbox--check' />
161
+ </div>
162
+ )
163
+ })
164
+ }
165
+
166
+ renderFieldDropdownInput = (com, idx) => {
167
+ let val = sb.find(this.props.submission.values, (f) => f.key === com.key) || {}
168
+ val = val.value || '""'
169
+ val = sb.parseJSON(val)
170
+ return (
171
+ <select
172
+ class='form-control'
173
+ ref={this[`${com.key}_input`]}
174
+ onChange={(e) => {
175
+ this.onChangeValue(com.key, e.target.value)
176
+ this.clearError(com.key)
177
+ }}
178
+ >
179
+ {sb.map(com.input_options, (option) => {
180
+ return <option value={option.value}>{option.label}</option>
181
+ })}
182
+ </select>
183
+ )
184
+ }
185
+
186
+ renderFieldTextareaInput = (com, idx) => {
187
+ let val = sb.find(this.props.submission.values, (f) => f.key === com.key) || {}
188
+ val = val.value || '""'
189
+ val = sb.parseJSON(val)
190
+ return (
191
+ <textarea
192
+ onInput={(e) => {
193
+ this.onChangeValue(com.key, e.target.value)
194
+ this.clearError(com.key)
195
+ }}
196
+ ref={this[`${com.key}_input`]}
197
+ rows='3'
198
+ class='form-control'
199
+ placeholder={com.placeholder}
200
+ />
201
+ )
202
+ }
203
+
204
+ onSubmit = (e) => {
205
+ e.preventDefault()
206
+ if (!this.validateForm()) return
207
+ this.props.onSubmit()
208
+ }
209
+
210
+ validateForm = () => {
211
+ let result = true
212
+ let ref = ''
213
+ let errors = {}
214
+ let components = sb.get(this.props.formSetting, 'components') || []
215
+ sb.each(components, (com) => {
216
+ if (com.type === 'input') {
217
+ if (com.is_required) {
218
+ let val = sb.find(this.props.submission.values, (f) => f.key === com.key) || {}
219
+ val = val.value || '""'
220
+ val = sb.parseJSON(val)
221
+ if (typeof val === 'string' && !val.trim()) {
222
+ result = false
223
+ errors[com.key] = this.props.t('cannot_be_empty')
224
+ if (!ref && com.input_type !== 'checkbox' && com.input_type !== 'radio') ref = `${com.key}_input`
225
+ }
226
+ if (Array.isArray(val) && val.length === 0) {
227
+ result = false
228
+ errors[com.key] = this.props.t('cannot_be_empty')
229
+ }
230
+ }
231
+ }
232
+ })
233
+ if (ref && this[ref] && this[ref].current) {
234
+ this[ref].current.focus()
235
+ }
236
+ this.setState({errorFields: errors})
237
+ return result
238
+ }
239
+
240
+ toggleRadioField(key, v) {
241
+ this.clearError(key)
242
+ this.onChangeValue(key, v)
243
+ }
244
+
245
+ toggleCheckboxField(key, v) {
246
+ let currentValues = sb.find(this.props.submission.values, (f) => f.key === key) || {}
247
+ currentValues = currentValues.value || '[]'
248
+ currentValues = sb.parseJSON(currentValues)
249
+
250
+ if (currentValues.includes(v)) {
251
+ currentValues = currentValues.filter((s) => s !== v)
252
+ } else {
253
+ currentValues.push(v)
254
+ }
255
+ this.clearError(key)
256
+ this.onChangeValue(key, currentValues)
257
+ }
258
+
259
+ onChangeValue = (key, v) => {
260
+ let formValues = sb.get(this.props.submission, 'values') || []
261
+ let f = sb.find(formValues, (field) => field.key === key)
262
+ if (!f) {
263
+ formValues.push({key, value: JSON.stringify(v)})
264
+ } else {
265
+ f.value = JSON.stringify(v)
266
+ }
267
+ this.props.onChangeFormValues(formValues)
268
+ }
269
+
270
+ clearError = (key) => {
271
+ let errors = this.state.errorFields
272
+ errors[key] = ''
273
+ this.setState({errorFields: errors})
274
+ }
275
+
276
+ render() {
277
+ let components = sb.get(this.props.formSetting, 'components') || []
278
+ let title = sb.get(this.props.formSetting, 'title') || ''
279
+ let cls = 'form-wrapper form'
280
+ if (this.props.mode === 'attachment') cls += ' form-wrapper-attachment'
281
+ return (
282
+ <div class={cls}>
283
+ {this.props.isSubmited && (
284
+ <div class='form-component'>{this.props.t('you_have_submitted_info_successfully')}</div>
285
+ )}
286
+ <form onSubmit={this.onSubmit}>
287
+ <div class='form-component form-component-block'>{title}</div>
288
+ {sb.map(components, this.renderFormComponent)}
289
+ </form>
290
+ </div>
291
+ )
292
+ }
293
+ }
@@ -0,0 +1,95 @@
1
+ import {Com} from '../common.js'
2
+ import {h, createRef} from 'preact'
3
+ import Form from './Form.js'
4
+ import sb from '../util/index.js'
5
+
6
+ export default class FormPage extends Com {
7
+ constructor(props) {
8
+ super(props)
9
+
10
+ this.state = {
11
+ submission: {},
12
+ formSetting: {},
13
+ submiting: false,
14
+ errorMessage: '',
15
+ isSubmited: false,
16
+
17
+ loading: false,
18
+ loadError: '',
19
+ }
20
+ }
21
+
22
+ componentDidMount() {
23
+ this.fetchFormSetting()
24
+ }
25
+
26
+ fetchFormSetting = async () => {
27
+ if (this.state.loading) return
28
+ this.setState({loading: true})
29
+ let res = await this.props.store.checkFormToken(this.props.token)
30
+ if (res.error) {
31
+ this.setState({loading: false, loadError: true})
32
+ return
33
+ }
34
+ let formSetting = sb.get(res, 'body.form') || {}
35
+ let submission = sb.get(res, 'body.submission') || {}
36
+ let isSubmited = sb.size(submission) > 0
37
+ this.setState({loading: false, loadError: false, formSetting, submission, isSubmited})
38
+ }
39
+
40
+ onChangeFormValues = (values) => {
41
+ this.setState({submission: {values}})
42
+ }
43
+
44
+ onSubmit = async () => {
45
+ this.setState({errorMessage: ''})
46
+ if (this.state.submiting) return
47
+ this.setState({submiting: true})
48
+ let res = await this.props.store.submitForm2(this.props.token, this.state.submission)
49
+ if (res.error) {
50
+ this.setState({submiting: false, errorMessage: sb.get(res, 'error.code')})
51
+ return
52
+ }
53
+ this.setState({submiting: false, isSubmited: true})
54
+ }
55
+
56
+ render() {
57
+ const {formSetting, loading, loadError, submission, errorMessage, isSubmited, submiting} = this.state
58
+ if (loading && !loadError) {
59
+ return (
60
+ <div class='h-full w-full d-flex justify-content-center align-items-center'>
61
+ <div class='text-muted text-xl'>{this.props.t('loading_data')} ...</div>
62
+ </div>
63
+ )
64
+ }
65
+ if (loadError) {
66
+ return (
67
+ <div class='h-full w-full d-flex justify-content-center align-items-center'>
68
+ <div class='text-center'>
69
+ <div class='text-danger text-lg mb-2'>{this.props.t('error_occurs_when_load_data')}</div>
70
+ <br />
71
+ <button disabled={loading} type='button' class='btn--start-convo' onClick={this.fetchFormSetting}>
72
+ {this.props.t('try_again')}
73
+ </button>
74
+ </div>
75
+ </div>
76
+ )
77
+ }
78
+ return (
79
+ <div class='form-page'>
80
+ <div class='form-page-header'>{'HEADER'}</div>
81
+ <Form
82
+ store={this.props.store}
83
+ formSetting={formSetting}
84
+ t={this.props.t}
85
+ submission={submission}
86
+ errorMessage={errorMessage}
87
+ isSubmited={isSubmited}
88
+ submiting={submiting}
89
+ onChangeFormValues={this.onChangeFormValues}
90
+ onSubmit={this.onSubmit}
91
+ />
92
+ </div>
93
+ )
94
+ }
95
+ }