@subiz/widget-v4 4.1.125 → 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.
- package/assets/less/avatar.less +7 -3
- package/assets/less/chatbox.less +25 -2
- package/assets/less/form.less +77 -2
- package/assets/less/message.less +4 -0
- package/assets/less/widget.less +20 -0
- package/assets/less/widget_header.less +27 -7
- package/compiled_src/chatbox/ChatButton.js +22 -7
- package/compiled_src/chatbox/ConnectionStatus.js +58 -0
- package/compiled_src/chatbox/Event.js +10 -8
- package/compiled_src/chatbox/FormAttachment2.js +80 -0
- package/compiled_src/chatbox/Header.js +12 -1
- package/compiled_src/chatbox/Widget.js +5 -0
- package/compiled_src/form/Form.js +274 -0
- package/compiled_src/form/FormPage.js +131 -0
- package/compiled_src/form/index.js +72 -0
- package/compiled_src/store/api.js +3 -0
- package/compiled_src/store/realtime_fake.js +4 -0
- package/compiled_src/store/store.js +25 -0
- package/compiled_src/util/campaign.js +3 -5
- package/compiled_src/util/common.js +15 -0
- package/dist/127.js +273 -0
- package/dist/132.js +2575 -6434
- package/dist/185.js +1885 -0
- package/dist/1967c022bc1.127.js +1 -0
- package/dist/1967c022bc1.132.js +1 -0
- package/dist/1967c022bc1.185.js +1 -0
- package/dist/1967c022bc1.27.js +1 -0
- package/dist/1967c022bc1.278.js +1 -0
- package/dist/1967c022bc1.459.js +1 -0
- package/dist/1967c022bc1.490.js +1 -0
- package/dist/1967c022bc1.74.js +1 -0
- package/dist/1967c022bc1.997.js +1 -0
- package/dist/1967c022bc1.app.js +1 -0
- package/dist/1967c022bc1.form.html +1 -0
- package/dist/1967c022bc1.form.js +1 -0
- package/dist/{18a02a9c54e.index.html → 1967c022bc1.index.html} +28 -4
- package/dist/{18a02a9c54e.library.html → 1967c022bc1.library.html} +28 -4
- package/dist/1967c022bc1.library.js +1 -0
- package/dist/{18a02a9c54e.preview.html → 1967c022bc1.preview.html} +1 -1
- package/dist/1967c022bc1.preview.js +1 -0
- package/dist/1967c022bc1.webview.js +1 -0
- package/dist/27.js +107 -0
- package/dist/278.js +406 -0
- package/dist/459.js +107 -0
- package/dist/{605.js → 490.js} +82 -82
- package/dist/{335.js → 74.js} +35 -35
- package/dist/{678.js → 997.js} +6 -6
- package/dist/HASH +1 -1
- package/dist/assets/img/{subiz-mono.877e9f5.svg → subiz-mono.8fbc067.svg} +1 -1
- package/dist/malicious-app.js +6 -4
- package/dist/screenshoot.html +1 -1
- package/dist/screenshoot.js +10133 -5182
- package/form.html +10 -0
- package/package.json +5 -4
- package/src/chatbox/ChatButton.js +22 -9
- package/src/chatbox/ConnectionStatus.js +42 -0
- package/src/chatbox/Event.js +10 -10
- package/src/chatbox/FormAttachment2.js +58 -0
- package/src/chatbox/Header.js +15 -0
- package/src/chatbox/Widget.js +2 -0
- package/src/form/Form.js +293 -0
- package/src/form/FormPage.js +95 -0
- package/src/form/index.js +79 -0
- package/src/store/api.js +8 -0
- package/src/store/realtime_fake.js +8 -4
- package/src/store/store.js +29 -0
- package/src/util/campaign.js +3 -5
- package/src/util/common.js +16 -0
- package/webpack.dev.js +2 -0
- package/webpack.prod.js +7 -0
- package/dist/178.js +0 -406
- package/dist/18a02a9c54e.132.js +0 -1
- package/dist/18a02a9c54e.178.js +0 -1
- package/dist/18a02a9c54e.213.js +0 -1
- package/dist/18a02a9c54e.335.js +0 -1
- package/dist/18a02a9c54e.362.js +0 -1
- package/dist/18a02a9c54e.441.js +0 -1
- package/dist/18a02a9c54e.605.js +0 -1
- package/dist/18a02a9c54e.678.js +0 -1
- package/dist/18a02a9c54e.782.js +0 -1
- package/dist/18a02a9c54e.app.js +0 -1
- package/dist/18a02a9c54e.library.js +0 -1
- package/dist/18a02a9c54e.preview.js +0 -1
- package/dist/18a02a9c54e.screenshoot.js +0 -1
- package/dist/18a02a9c54e.webview.js +0 -1
- package/dist/213.js +0 -2780
- package/dist/362.js +0 -107
- package/dist/441.js +0 -2855
- package/dist/782.js +0 -107
package/form.html
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@subiz/widget-v4",
|
|
3
|
-
"version": "4.1.
|
|
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.
|
|
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
|
-
"
|
|
33
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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,9 +388,20 @@ 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
|
-
|
|
391
|
+
let format = sb.get(ev, 'data.message.format')
|
|
390
392
|
let block = sb.get(ev, 'data.message.block')
|
|
391
|
-
if (block || format == 'block')
|
|
393
|
+
if (block || format == 'block') {
|
|
394
|
+
if (sb.isBlockEmpty(block)) {
|
|
395
|
+
let attachments = sb.get(ev, 'data.message.attachments')
|
|
396
|
+
if (sb.size(attachments) > 0) {
|
|
397
|
+
return (
|
|
398
|
+
<div>{`${this.props.t('send')} ${sb.size(attachments)} ${sb.size(attachments) > 1 ? this.props.t('attachments') : this.props.t('attachment')}`}</div>
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
return defmsg
|
|
402
|
+
}
|
|
403
|
+
return <Block block={block} />
|
|
404
|
+
}
|
|
392
405
|
|
|
393
406
|
let message = sb.get(ev, 'data.message.text', defmsg)
|
|
394
407
|
if (!message && sb.size(sb.get(ev, 'data.message.attachments')) > 0) message = defmsg
|
|
@@ -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
|
+
}
|
package/src/chatbox/Event.js
CHANGED
|
@@ -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
|
|
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=
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
+
}
|
package/src/chatbox/Header.js
CHANGED
|
@@ -9,6 +9,20 @@ export default class Header extends Com {
|
|
|
9
9
|
super(props)
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
renderAccountAvatar() {
|
|
13
|
+
let acc = this.props.store.matchAccount() || {}
|
|
14
|
+
if (!acc.logo_url) return null
|
|
15
|
+
return (
|
|
16
|
+
<img
|
|
17
|
+
width={38}
|
|
18
|
+
height={38}
|
|
19
|
+
alt={acc.name}
|
|
20
|
+
class='avatar avatar__account'
|
|
21
|
+
src={this.props.store.replaceFileUrl(acc.logo_url) || require('../../assets/img/image_default.png')}
|
|
22
|
+
/>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
12
26
|
render() {
|
|
13
27
|
let headerMode = 'collapsed'
|
|
14
28
|
|
|
@@ -55,6 +69,7 @@ export default class Header extends Com {
|
|
|
55
69
|
return (
|
|
56
70
|
<div class={cls} style={style}>
|
|
57
71
|
{$back}
|
|
72
|
+
{this.renderAccountAvatar()}
|
|
58
73
|
<div class='widget-header--title-container'>
|
|
59
74
|
<div class='widget-header--title'>{this.getTitle()}</div>
|
|
60
75
|
<div class='widget-header--description' title={desc}>
|
package/src/chatbox/Widget.js
CHANGED
|
@@ -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}
|
package/src/form/Form.js
ADDED
|
@@ -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
|
+
}
|