@papercups-io/chat-widget
Advanced tools
Comparing version 1.0.15 to 1.0.16-beta.0
@@ -6,754 +6,5 @@ function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var themeUi = require('theme-ui'); | ||
var phoenix = require('phoenix'); | ||
var dayjs = _interopDefault(require('dayjs')); | ||
var utc = _interopDefault(require('dayjs/plugin/utc')); | ||
var request = _interopDefault(require('superagent')); | ||
var qs = _interopDefault(require('query-string')); | ||
var tinycolor = _interopDefault(require('tinycolor2')); | ||
function _extends() { | ||
_extends = Object.assign || function (target) { | ||
for (var i = 1; i < arguments.length; i++) { | ||
var source = arguments[i]; | ||
for (var key in source) { | ||
if (Object.prototype.hasOwnProperty.call(source, key)) { | ||
target[key] = source[key]; | ||
} | ||
} | ||
} | ||
return target; | ||
}; | ||
return _extends.apply(this, arguments); | ||
} | ||
function _inheritsLoose(subClass, superClass) { | ||
subClass.prototype = Object.create(superClass.prototype); | ||
subClass.prototype.constructor = subClass; | ||
subClass.__proto__ = superClass; | ||
} | ||
// A type of promise-like that resolves synchronously and supports only one observer | ||
const _iteratorSymbol = /*#__PURE__*/ typeof Symbol !== "undefined" ? (Symbol.iterator || (Symbol.iterator = Symbol("Symbol.iterator"))) : "@@iterator"; | ||
const _asyncIteratorSymbol = /*#__PURE__*/ typeof Symbol !== "undefined" ? (Symbol.asyncIterator || (Symbol.asyncIterator = Symbol("Symbol.asyncIterator"))) : "@@asyncIterator"; | ||
// Asynchronously call a function and send errors to recovery continuation | ||
function _catch(body, recover) { | ||
try { | ||
var result = body(); | ||
} catch(e) { | ||
return recover(e); | ||
} | ||
if (result && result.then) { | ||
return result.then(void 0, recover); | ||
} | ||
return result; | ||
} | ||
var BotIcon = function BotIcon(_ref) { | ||
var width = _ref.width, | ||
height = _ref.height, | ||
fill = _ref.fill, | ||
className = _ref.className; | ||
return themeUi.jsx("svg", { | ||
viewBox: '64 64 896 896', | ||
focusable: 'false', | ||
className: className, | ||
"data-icon": 'robot', | ||
width: width || '1em', | ||
height: height || '1em', | ||
sx: { | ||
fill: fill || 'black' | ||
} | ||
}, themeUi.jsx("path", { | ||
d: 'M300 328a60 60 0 10120 0 60 60 0 10-120 0zM852 64H172c-17.7 0-32 14.3-32 32v660c0 17.7 14.3 32 32 32h680c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-32 660H204V128h616v596zM604 328a60 60 0 10120 0 60 60 0 10-120 0zm250.2 556H169.8c-16.5 0-29.8 14.3-29.8 32v36c0 4.4 3.3 8 7.4 8h729.1c4.1 0 7.4-3.6 7.4-8v-36c.1-17.7-13.2-32-29.7-32zM664 508H360c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h304c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z' | ||
})); | ||
}; | ||
dayjs.extend(utc); | ||
var formatRelativeTime = function formatRelativeTime(date) { | ||
var ms = dayjs().diff(date, 'second'); | ||
var mins = Math.floor(ms / 60); | ||
var hrs = Math.floor(mins / 60); | ||
var days = Math.floor(hrs / 24); | ||
if (ms < 10) { | ||
return 'just now'; | ||
} else if (ms < 60) { | ||
return ms + " seconds ago"; | ||
} else if (mins <= 60) { | ||
return mins + " minute" + (mins === 1 ? '' : 's') + " ago"; | ||
} else if (hrs <= 24) { | ||
return hrs + " hour" + (hrs === 1 ? '' : 's') + " ago"; | ||
} else { | ||
return days + " day" + (days === 1 ? '' : 's') + " ago"; | ||
} | ||
}; | ||
var getAgentIdentifier = function getAgentIdentifier(user) { | ||
if (!user) { | ||
return 'Agent'; | ||
} | ||
var display_name = user.display_name, | ||
full_name = user.full_name, | ||
email = user.email; | ||
var _email$split = email.split('@'), | ||
username = _email$split[0]; | ||
return display_name || full_name || username || 'Agent'; | ||
}; | ||
var SenderAvatar = function SenderAvatar(_ref) { | ||
var name = _ref.name, | ||
user = _ref.user, | ||
isBot = _ref.isBot; | ||
var profilePhotoUrl = user && user.profile_photo_url; | ||
if (profilePhotoUrl) { | ||
return React.createElement(themeUi.Box, { | ||
mr: 2, | ||
style: { | ||
height: 32, | ||
width: 32, | ||
borderRadius: '50%', | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
backgroundPosition: 'center', | ||
backgroundSize: 'cover', | ||
backgroundImage: "url(" + profilePhotoUrl + ")" | ||
} | ||
}); | ||
} | ||
return React.createElement(themeUi.Flex, { | ||
mr: 2, | ||
sx: { | ||
bg: isBot ? 'gray' : 'primary', | ||
height: 32, | ||
width: 32, | ||
borderRadius: '50%', | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
color: '#fff' | ||
} | ||
}, isBot ? React.createElement(BotIcon, { | ||
fill: 'background', | ||
height: 16, | ||
width: 16 | ||
}) : name.slice(0, 1).toUpperCase()); | ||
}; | ||
var ChatMessage = function ChatMessage(_ref2) { | ||
var message = _ref2.message, | ||
isMe = _ref2.isMe, | ||
isLastInGroup = _ref2.isLastInGroup, | ||
shouldDisplayTimestamp = _ref2.shouldDisplayTimestamp; | ||
var body = message.body, | ||
created_at = message.created_at, | ||
user = message.user, | ||
type = message.type; | ||
var created = dayjs.utc(created_at); | ||
var timestamp = formatRelativeTime(created); | ||
var isBot = type === 'bot'; | ||
var identifer = isBot ? 'Bot' : getAgentIdentifier(user); | ||
if (isMe) { | ||
return React.createElement(themeUi.Box, { | ||
pr: 0, | ||
pl: 4, | ||
pb: isLastInGroup ? 3 : 2 | ||
}, React.createElement(themeUi.Flex, { | ||
sx: { | ||
justifyContent: 'flex-end' | ||
} | ||
}, React.createElement(themeUi.Box, { | ||
px: '14px', | ||
py: 2, | ||
sx: { | ||
color: 'background', | ||
bg: 'primary', | ||
borderRadius: 4 | ||
} | ||
}, React.createElement(themeUi.Text, null, body))), shouldDisplayTimestamp && React.createElement(themeUi.Flex, { | ||
m: 1, | ||
sx: { | ||
justifyContent: 'flex-end' | ||
} | ||
}, React.createElement(themeUi.Text, { | ||
sx: { | ||
color: 'gray' | ||
} | ||
}, "Sent ", timestamp))); | ||
} | ||
return React.createElement(themeUi.Box, { | ||
pr: 4, | ||
pl: 0, | ||
pb: isLastInGroup ? 3 : 2 | ||
}, React.createElement(themeUi.Flex, { | ||
sx: { | ||
justifyContent: 'flex-start', | ||
alignItems: 'center' | ||
} | ||
}, React.createElement(SenderAvatar, { | ||
name: identifer, | ||
user: user, | ||
isBot: isBot | ||
}), React.createElement(themeUi.Box, { | ||
px: '14px', | ||
py: 2, | ||
sx: { | ||
color: 'text', | ||
bg: 'rgb(245, 245, 245)', | ||
borderRadius: 4, | ||
maxWidth: '80%' | ||
} | ||
}, body)), shouldDisplayTimestamp && React.createElement(themeUi.Flex, { | ||
m: 1, | ||
sx: { | ||
justifyContent: 'flex-start' | ||
} | ||
}, React.createElement(themeUi.Text, { | ||
sx: { | ||
color: 'gray' | ||
} | ||
}, identifer, " \xB7 Sent ", timestamp))); | ||
}; | ||
var SendIcon = function SendIcon(_ref) { | ||
var width = _ref.width, | ||
height = _ref.height, | ||
fill = _ref.fill, | ||
className = _ref.className; | ||
return themeUi.jsx("svg", { | ||
focusable: 'false', | ||
"aria-hidden": 'true', | ||
height: height || 16, | ||
width: width || 16, | ||
viewBox: '0 0 15 16', | ||
className: className, | ||
sx: { | ||
fill: fill || 'black' | ||
} | ||
}, themeUi.jsx("g", { | ||
transform: 'translate(-24.000000, -12.000000)' | ||
}, themeUi.jsx("path", { | ||
d: 'M25.4036262,27.3409362 C24.4176893,27.8509036 23.8195834,27.3951055 24.0683403,26.3201996 L25.0887779,21.910776 C25.2131242,21.3734618 25.7510472,20.8884231 26.3078778,20.8254187 L32.503417,20.1244045 C34.151155,19.9379658 34.1569707,19.6389088 32.503417,19.4549971 L26.3078778,18.7659164 C25.7589338,18.7048617 25.2129433,18.217839 25.0887779,17.6798715 L24.0683403,13.2586546 C23.8198614,12.1820783 24.408944,11.7182276 25.4036262,12.2327184 L38.22304,18.8634497 C39.208977,19.373417 39.2177223,20.1957141 38.22304,20.7102049 L25.4036262,27.3409362 Z' | ||
}))); | ||
}; | ||
var DEFAULT_BASE_URL = 'https://app.papercups.io'; | ||
var getWebsocketUrl = function getWebsocketUrl(baseUrl) { | ||
if (baseUrl === void 0) { | ||
baseUrl = DEFAULT_BASE_URL; | ||
} | ||
var _baseUrl$split = baseUrl.split('://'), | ||
protocol = _baseUrl$split[0], | ||
host = _baseUrl$split[1]; | ||
var isHttps = protocol === 'https'; | ||
return (isHttps ? 'wss' : 'ws') + "://" + host + "/socket"; | ||
}; | ||
function now() { | ||
var date = new Date(); | ||
return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds()); | ||
} | ||
var EMPTY_METADATA = {}; | ||
var createNewCustomer = function createNewCustomer(accountId, metadata, baseUrl) { | ||
if (metadata === void 0) { | ||
metadata = EMPTY_METADATA; | ||
} | ||
if (baseUrl === void 0) { | ||
baseUrl = DEFAULT_BASE_URL; | ||
} | ||
try { | ||
return Promise.resolve(request.post(baseUrl + "/api/customers").send({ | ||
customer: _extends({}, metadata, { | ||
account_id: accountId, | ||
first_seen: now(), | ||
last_seen: now() | ||
}) | ||
}).then(function (res) { | ||
return res.body.data; | ||
})); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
var updateCustomerMetadata = function updateCustomerMetadata(customerId, metadata, baseUrl) { | ||
if (metadata === void 0) { | ||
metadata = EMPTY_METADATA; | ||
} | ||
if (baseUrl === void 0) { | ||
baseUrl = DEFAULT_BASE_URL; | ||
} | ||
try { | ||
return Promise.resolve(request.put(baseUrl + "/api/customers/" + customerId + "/metadata").send({ | ||
metadata: metadata | ||
}).then(function (res) { | ||
return res.body.data; | ||
})); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
var createNewConversation = function createNewConversation(accountId, customerId, baseUrl) { | ||
if (baseUrl === void 0) { | ||
baseUrl = DEFAULT_BASE_URL; | ||
} | ||
try { | ||
return Promise.resolve(request.post(baseUrl + "/api/conversations").send({ | ||
conversation: { | ||
account_id: accountId, | ||
customer_id: customerId | ||
} | ||
}).then(function (res) { | ||
return res.body.data; | ||
})); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
var fetchCustomerConversations = function fetchCustomerConversations(customerId, accountId, baseUrl) { | ||
if (baseUrl === void 0) { | ||
baseUrl = DEFAULT_BASE_URL; | ||
} | ||
try { | ||
return Promise.resolve(request.get(baseUrl + "/api/conversations/customer").query({ | ||
customer_id: customerId, | ||
account_id: accountId | ||
}).then(function (res) { | ||
return res.body.data; | ||
})); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
var PREFIX = '__PAPERCUPS__'; | ||
var get = function get(key) { | ||
var result = localStorage.getItem("" + PREFIX + key); | ||
if (!result) { | ||
return null; | ||
} | ||
try { | ||
return JSON.parse(result); | ||
} catch (e) { | ||
return result; | ||
} | ||
}; | ||
var set = function set(key, value) { | ||
localStorage.setItem("" + PREFIX + key, JSON.stringify(value)); | ||
}; | ||
var getCustomerId = function getCustomerId() { | ||
return get('__CUSTOMER_ID__'); | ||
}; | ||
var setCustomerId = function setCustomerId(id) { | ||
return set('__CUSTOMER_ID__', id); | ||
}; | ||
var ChatWindow = /*#__PURE__*/function (_React$Component) { | ||
_inheritsLoose(ChatWindow, _React$Component); | ||
function ChatWindow() { | ||
var _this; | ||
_this = _React$Component.apply(this, arguments) || this; | ||
_this.scrollToEl = null; | ||
_this.state = { | ||
message: '', | ||
messages: [], | ||
customerId: getCustomerId(), | ||
conversationId: null, | ||
isSending: false | ||
}; | ||
_this.getDefaultGreeting = function () { | ||
var greeting = _this.props.greeting; | ||
if (!greeting) { | ||
return []; | ||
} | ||
return [{ | ||
type: 'bot', | ||
body: greeting, | ||
created_at: now().toString() | ||
}]; | ||
}; | ||
_this.fetchLatestConversation = function (customerId) { | ||
try { | ||
if (!customerId) { | ||
_this.setState({ | ||
messages: [].concat(_this.getDefaultGreeting()) | ||
}); | ||
return Promise.resolve(); | ||
} | ||
var _this$props = _this.props, | ||
accountId = _this$props.accountId, | ||
baseUrl = _this$props.baseUrl, | ||
metadata = _this$props.customer; | ||
console.log('Fetching conversations for customer:', customerId); | ||
return Promise.resolve(_catch(function () { | ||
return Promise.resolve(fetchCustomerConversations(customerId, accountId, baseUrl)).then(function (conversations) { | ||
console.log('Found existing conversations:', conversations); | ||
if (!conversations || !conversations.length) { | ||
_this.setState({ | ||
messages: [].concat(_this.getDefaultGreeting()) | ||
}); | ||
return; | ||
} | ||
var latest = conversations[0]; | ||
var conversationId = latest.id, | ||
_latest$messages = latest.messages, | ||
messages = _latest$messages === void 0 ? [] : _latest$messages; | ||
var formattedMessages = messages.sort(function (a, b) { | ||
return +new Date(a.created_at) - +new Date(b.created_at); | ||
}); | ||
_this.setState({ | ||
conversationId: conversationId, | ||
messages: [].concat(_this.getDefaultGreeting(), formattedMessages) | ||
}); | ||
_this.joinConversationChannel(conversationId, customerId); | ||
return Promise.resolve(_this.updateExistingCustomer(customerId, metadata)).then(function () {}); | ||
}); | ||
}, function (err) { | ||
console.log('Error fetching conversations!', err); | ||
})); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
_this.createNewCustomerId = function (accountId) { | ||
try { | ||
var _this$props2 = _this.props, | ||
baseUrl = _this$props2.baseUrl, | ||
metadata = _this$props2.customer; | ||
return Promise.resolve(createNewCustomer(accountId, metadata, baseUrl)).then(function (_ref) { | ||
var customerId = _ref.id; | ||
setCustomerId(customerId); | ||
return customerId; | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
_this.updateExistingCustomer = function (customerId, metadata) { | ||
try { | ||
if (!metadata) { | ||
return Promise.resolve(); | ||
} | ||
var _temp2 = _catch(function () { | ||
var baseUrl = _this.props.baseUrl; | ||
return Promise.resolve(updateCustomerMetadata(customerId, metadata, baseUrl)).then(function () {}); | ||
}, function (err) { | ||
console.log('Error updating customer metadata!', err); | ||
}); | ||
return Promise.resolve(_temp2 && _temp2.then ? _temp2.then(function () {}) : void 0); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
_this.initializeNewConversation = function () { | ||
try { | ||
var _this$props3 = _this.props, | ||
accountId = _this$props3.accountId, | ||
baseUrl = _this$props3.baseUrl; | ||
return Promise.resolve(_this.createNewCustomerId(accountId)).then(function (customerId) { | ||
return Promise.resolve(createNewConversation(accountId, customerId, baseUrl)).then(function (_ref2) { | ||
var conversationId = _ref2.id; | ||
_this.setState({ | ||
customerId: customerId, | ||
conversationId: conversationId | ||
}); | ||
_this.joinConversationChannel(conversationId, customerId); | ||
return { | ||
customerId: customerId, | ||
conversationId: conversationId | ||
}; | ||
}); | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
_this.joinConversationChannel = function (conversationId, customerId) { | ||
if (_this.channel && _this.channel.leave) { | ||
_this.channel.leave(); | ||
} | ||
console.log('Joining channel:', conversationId); | ||
_this.channel = _this.socket.channel("conversation:" + conversationId, { | ||
customer_id: customerId | ||
}); | ||
_this.channel.on('shout', function (message) { | ||
_this.handleNewMessage(message); | ||
}); | ||
_this.channel.join().receive('ok', function (res) { | ||
console.log('Joined successfully!', res); | ||
}).receive('error', function (err) { | ||
console.log('Unable to join!', err); | ||
}); | ||
_this.scrollToEl.scrollIntoView(); | ||
}; | ||
_this.handleNewMessage = function (message) { | ||
_this.setState({ | ||
messages: [].concat(_this.state.messages, [message]) | ||
}, function () { | ||
_this.scrollToEl.scrollIntoView(); | ||
}); | ||
}; | ||
_this.handleMessageChange = function (e) { | ||
_this.setState({ | ||
message: e.target.value | ||
}); | ||
}; | ||
_this.handleKeyDown = function (e) { | ||
if (e.key === 'Enter') { | ||
_this.handleSendMessage(e); | ||
} | ||
}; | ||
_this.handleSendMessage = function (e) { | ||
try { | ||
var _temp5 = function _temp5() { | ||
if (!_this.channel) { | ||
_this.setState({ | ||
isSending: false | ||
}); | ||
return; | ||
} | ||
_this.channel.push('shout', { | ||
body: message, | ||
customer_id: _this.state.customerId | ||
}); | ||
_this.setState({ | ||
message: '', | ||
isSending: false | ||
}); | ||
}; | ||
e && e.preventDefault(); | ||
var _this$state = _this.state, | ||
message = _this$state.message, | ||
customerId = _this$state.customerId, | ||
conversationId = _this$state.conversationId, | ||
isSending = _this$state.isSending; | ||
if (isSending || !message || message.trim().length === 0) { | ||
return Promise.resolve(); | ||
} | ||
_this.setState({ | ||
isSending: true | ||
}); | ||
var _temp6 = function () { | ||
if (!customerId || !conversationId) { | ||
return Promise.resolve(_this.initializeNewConversation()).then(function () {}); | ||
} | ||
}(); | ||
return Promise.resolve(_temp6 && _temp6.then ? _temp6.then(_temp5) : _temp5(_temp6)); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
return _this; | ||
} | ||
var _proto = ChatWindow.prototype; | ||
_proto.componentDidMount = function componentDidMount() { | ||
var websocketUrl = getWebsocketUrl(this.props.baseUrl); | ||
this.socket = new phoenix.Socket(websocketUrl); | ||
this.socket.connect(); | ||
this.fetchLatestConversation(this.state.customerId); | ||
}; | ||
_proto.componentWillUnmount = function componentWillUnmount() { | ||
this.channel && this.channel.leave(); | ||
}; | ||
_proto.render = function render() { | ||
var _this2 = this; | ||
var _this$props4 = this.props, | ||
_this$props4$title = _this$props4.title, | ||
title = _this$props4$title === void 0 ? 'Welcome!' : _this$props4$title, | ||
_this$props4$subtitle = _this$props4.subtitle, | ||
subtitle = _this$props4$subtitle === void 0 ? 'How can we help you?' : _this$props4$subtitle; | ||
var _this$state2 = this.state, | ||
customerId = _this$state2.customerId, | ||
message = _this$state2.message, | ||
_this$state2$messages = _this$state2.messages, | ||
messages = _this$state2$messages === void 0 ? [] : _this$state2$messages, | ||
isSending = _this$state2.isSending; | ||
return React.createElement(themeUi.Flex, { | ||
sx: { | ||
bg: 'background', | ||
flexDirection: 'column', | ||
height: '100%', | ||
width: '100%' | ||
} | ||
}, React.createElement(themeUi.Box, { | ||
py: 3, | ||
px: 4, | ||
sx: { | ||
bg: 'primary' | ||
} | ||
}, React.createElement(themeUi.Heading, { | ||
as: 'h2', | ||
className: 'Papercups-heading', | ||
sx: { | ||
color: 'background', | ||
my: 1 | ||
} | ||
}, title), React.createElement(themeUi.Text, { | ||
sx: { | ||
color: 'offset' | ||
} | ||
}, subtitle)), React.createElement(themeUi.Box, { | ||
p: 3, | ||
sx: { | ||
flex: 1, | ||
boxShadow: 'rgba(0, 0, 0, 0.2) 0px 21px 4px -20px inset', | ||
overflowY: 'scroll' | ||
} | ||
}, messages.map(function (msg, key) { | ||
var next = messages[key + 1]; | ||
var isLastInGroup = next ? msg.customer_id !== next.customer_id : true; | ||
var shouldDisplayTimestamp = key === messages.length - 1; | ||
var isMe = msg.customer_id === customerId; | ||
return React.createElement(framerMotion.motion.div, { | ||
key: key, | ||
initial: { | ||
opacity: 0, | ||
x: isMe ? 2 : -2 | ||
}, | ||
animate: { | ||
opacity: 1, | ||
x: 0 | ||
}, | ||
transition: { | ||
duration: 0.2, | ||
ease: 'easeIn' | ||
} | ||
}, React.createElement(ChatMessage, { | ||
key: key, | ||
message: msg, | ||
isMe: isMe, | ||
isLastInGroup: isLastInGroup, | ||
shouldDisplayTimestamp: shouldDisplayTimestamp | ||
})); | ||
}), React.createElement("div", { | ||
ref: function ref(el) { | ||
return _this2.scrollToEl = el; | ||
} | ||
})), React.createElement(themeUi.Box, { | ||
p: 2, | ||
sx: { | ||
borderTop: '1px solid rgb(230, 230, 230)', | ||
boxShadow: 'rgba(0, 0, 0, 0.1) 0px 0px 100px 0px' | ||
} | ||
}, React.createElement(themeUi.Flex, { | ||
sx: { | ||
alignItems: 'center' | ||
} | ||
}, React.createElement(themeUi.Box, { | ||
mr: 3, | ||
sx: { | ||
flex: 1 | ||
} | ||
}, React.createElement(themeUi.Textarea, { | ||
sx: { | ||
fontFamily: 'body', | ||
color: 'input', | ||
variant: 'styles.textarea.transparent' | ||
}, | ||
className: 'TextArea--transparent', | ||
placeholder: 'Start typing...', | ||
rows: 1, | ||
autoFocus: true, | ||
value: message, | ||
onKeyDown: this.handleKeyDown, | ||
onChange: this.handleMessageChange | ||
})), React.createElement(themeUi.Box, { | ||
pl: 3 | ||
}, React.createElement(themeUi.Button, { | ||
variant: 'primary', | ||
type: 'submit', | ||
disabled: isSending, | ||
onClick: this.handleSendMessage, | ||
sx: { | ||
display: 'flex', | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
borderRadius: '50%', | ||
height: '36px', | ||
width: '36px', | ||
padding: 0 | ||
} | ||
}, React.createElement(SendIcon, { | ||
width: 16, | ||
height: 16, | ||
fill: 'background' | ||
}))))), React.createElement("img", { | ||
src: 'https://papercups.s3.us-east-2.amazonaws.com/papercups-logo.svg', | ||
width: '0', | ||
height: '0' | ||
})); | ||
}; | ||
return ChatWindow; | ||
}(React.Component); | ||
var Path = function Path(props) { | ||
@@ -1048,2 +299,26 @@ return React.createElement(framerMotion.motion.path, Object.assign({ | ||
var IFRAME_URL = 'https://chat-window.vercel.app'; | ||
var setup = function setup(w, handlers) { | ||
var cb = function cb(msg) { | ||
if (msg.origin !== IFRAME_URL) { | ||
return; | ||
} | ||
handlers(msg); | ||
}; | ||
if (w.addEventListener) { | ||
w.addEventListener('message', cb); | ||
return function () { | ||
return w.removeEventListener('message', cb); | ||
}; | ||
} else { | ||
w.attachEvent('onmessage', cb); | ||
return function () { | ||
return w.detachEvent('message', cb); | ||
}; | ||
} | ||
}; | ||
var EmbeddableWidget = function EmbeddableWidget(_ref) { | ||
@@ -1060,6 +335,15 @@ var accountId = _ref.accountId, | ||
var _React$useState = React.useState(defaultIsOpen), | ||
var _React$useState = React.useState(false), | ||
isOpen = _React$useState[0], | ||
setIsOpen = _React$useState[1]; | ||
var iframeRef = React.useRef(null); | ||
var query = qs.stringify({ | ||
accountId: accountId, | ||
title: title, | ||
subtitle: subtitle, | ||
primaryColor: primaryColor, | ||
baseUrl: baseUrl, | ||
greeting: greeting | ||
}); | ||
var theme = getThemeConfig({ | ||
@@ -1069,2 +353,56 @@ primary: primaryColor | ||
var send = function send(event, payload) { | ||
console.log('Sending from parent:', { | ||
event: event, | ||
payload: payload | ||
}); | ||
var el = iframeRef.current; | ||
el.contentWindow.postMessage({ | ||
event: event, | ||
payload: payload | ||
}, '*'); | ||
}; | ||
var handleChatLoaded = function handleChatLoaded() { | ||
if (defaultIsOpen) { | ||
setIsOpen(true); | ||
} | ||
return send('papercups:ping'); | ||
}; | ||
var sendCustomerUpdate = function sendCustomerUpdate(payload) { | ||
var customerId = payload.customerId; | ||
return send('customer:update', { | ||
customerId: customerId, | ||
metadata: customer | ||
}); | ||
}; | ||
var handlers = function handlers(msg) { | ||
console.log('Handling in parent:', msg.data); | ||
var _msg$data = msg.data, | ||
event = _msg$data.event, | ||
_msg$data$payload = _msg$data.payload, | ||
payload = _msg$data$payload === void 0 ? {} : _msg$data$payload; | ||
switch (event) { | ||
case 'chat:loaded': | ||
return handleChatLoaded(); | ||
case 'conversation:join': | ||
return sendCustomerUpdate(payload); | ||
default: | ||
return null; | ||
} | ||
}; | ||
React.useEffect(function () { | ||
var unsubscribe = setup(window, handlers); | ||
return function () { | ||
return unsubscribe(); | ||
}; | ||
}, []); | ||
var handleToggleOpen = function handleToggleOpen() { | ||
@@ -1076,12 +414,16 @@ return setIsOpen(!isOpen); | ||
theme: theme | ||
}, isOpen && themeUi.jsx(framerMotion.motion.div, { | ||
}, themeUi.jsx(framerMotion.motion.iframe, { | ||
ref: iframeRef, | ||
className: 'Papercups-chatWindowContainer', | ||
initial: { | ||
opacity: 0, | ||
y: 4 | ||
animate: isOpen ? 'open' : 'closed', | ||
variants: { | ||
closed: { | ||
opacity: 0, | ||
y: 4 | ||
}, | ||
open: { | ||
opacity: 1, | ||
y: 0 | ||
} | ||
}, | ||
animate: { | ||
opacity: 1, | ||
y: 0 | ||
}, | ||
transition: { | ||
@@ -1091,13 +433,12 @@ duration: 0.2, | ||
}, | ||
src: IFRAME_URL + "?" + query, | ||
style: isOpen ? {} : { | ||
bottom: -9999 | ||
}, | ||
sx: { | ||
border: 'none', | ||
bg: 'background', | ||
variant: 'styles.WidgetContainer' | ||
} | ||
}, themeUi.jsx(ChatWindow, { | ||
title: title, | ||
subtitle: subtitle, | ||
accountId: accountId, | ||
greeting: greeting, | ||
customer: customer, | ||
baseUrl: baseUrl | ||
})), themeUi.jsx(framerMotion.motion.div, { | ||
}, "Loading..."), themeUi.jsx(framerMotion.motion.div, { | ||
className: 'Papercups-toggleButtonContainer', | ||
@@ -1104,0 +445,0 @@ initial: false, |
import React from 'react'; | ||
import { motion } from 'framer-motion'; | ||
import { jsx, Box, Flex, Text, Heading, Textarea, Button, ThemeProvider } from 'theme-ui'; | ||
import { Socket } from 'phoenix'; | ||
import dayjs from 'dayjs'; | ||
import utc from 'dayjs/plugin/utc'; | ||
import request from 'superagent'; | ||
import { Flex, Button, jsx, ThemeProvider } from 'theme-ui'; | ||
import qs from 'query-string'; | ||
import tinycolor from 'tinycolor2'; | ||
function _extends() { | ||
_extends = Object.assign || function (target) { | ||
for (var i = 1; i < arguments.length; i++) { | ||
var source = arguments[i]; | ||
for (var key in source) { | ||
if (Object.prototype.hasOwnProperty.call(source, key)) { | ||
target[key] = source[key]; | ||
} | ||
} | ||
} | ||
return target; | ||
}; | ||
return _extends.apply(this, arguments); | ||
} | ||
function _inheritsLoose(subClass, superClass) { | ||
subClass.prototype = Object.create(superClass.prototype); | ||
subClass.prototype.constructor = subClass; | ||
subClass.__proto__ = superClass; | ||
} | ||
// A type of promise-like that resolves synchronously and supports only one observer | ||
const _iteratorSymbol = /*#__PURE__*/ typeof Symbol !== "undefined" ? (Symbol.iterator || (Symbol.iterator = Symbol("Symbol.iterator"))) : "@@iterator"; | ||
const _asyncIteratorSymbol = /*#__PURE__*/ typeof Symbol !== "undefined" ? (Symbol.asyncIterator || (Symbol.asyncIterator = Symbol("Symbol.asyncIterator"))) : "@@asyncIterator"; | ||
// Asynchronously call a function and send errors to recovery continuation | ||
function _catch(body, recover) { | ||
try { | ||
var result = body(); | ||
} catch(e) { | ||
return recover(e); | ||
} | ||
if (result && result.then) { | ||
return result.then(void 0, recover); | ||
} | ||
return result; | ||
} | ||
var BotIcon = function BotIcon(_ref) { | ||
var width = _ref.width, | ||
height = _ref.height, | ||
fill = _ref.fill, | ||
className = _ref.className; | ||
return jsx("svg", { | ||
viewBox: '64 64 896 896', | ||
focusable: 'false', | ||
className: className, | ||
"data-icon": 'robot', | ||
width: width || '1em', | ||
height: height || '1em', | ||
sx: { | ||
fill: fill || 'black' | ||
} | ||
}, jsx("path", { | ||
d: 'M300 328a60 60 0 10120 0 60 60 0 10-120 0zM852 64H172c-17.7 0-32 14.3-32 32v660c0 17.7 14.3 32 32 32h680c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-32 660H204V128h616v596zM604 328a60 60 0 10120 0 60 60 0 10-120 0zm250.2 556H169.8c-16.5 0-29.8 14.3-29.8 32v36c0 4.4 3.3 8 7.4 8h729.1c4.1 0 7.4-3.6 7.4-8v-36c.1-17.7-13.2-32-29.7-32zM664 508H360c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h304c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z' | ||
})); | ||
}; | ||
dayjs.extend(utc); | ||
var formatRelativeTime = function formatRelativeTime(date) { | ||
var ms = dayjs().diff(date, 'second'); | ||
var mins = Math.floor(ms / 60); | ||
var hrs = Math.floor(mins / 60); | ||
var days = Math.floor(hrs / 24); | ||
if (ms < 10) { | ||
return 'just now'; | ||
} else if (ms < 60) { | ||
return ms + " seconds ago"; | ||
} else if (mins <= 60) { | ||
return mins + " minute" + (mins === 1 ? '' : 's') + " ago"; | ||
} else if (hrs <= 24) { | ||
return hrs + " hour" + (hrs === 1 ? '' : 's') + " ago"; | ||
} else { | ||
return days + " day" + (days === 1 ? '' : 's') + " ago"; | ||
} | ||
}; | ||
var getAgentIdentifier = function getAgentIdentifier(user) { | ||
if (!user) { | ||
return 'Agent'; | ||
} | ||
var display_name = user.display_name, | ||
full_name = user.full_name, | ||
email = user.email; | ||
var _email$split = email.split('@'), | ||
username = _email$split[0]; | ||
return display_name || full_name || username || 'Agent'; | ||
}; | ||
var SenderAvatar = function SenderAvatar(_ref) { | ||
var name = _ref.name, | ||
user = _ref.user, | ||
isBot = _ref.isBot; | ||
var profilePhotoUrl = user && user.profile_photo_url; | ||
if (profilePhotoUrl) { | ||
return React.createElement(Box, { | ||
mr: 2, | ||
style: { | ||
height: 32, | ||
width: 32, | ||
borderRadius: '50%', | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
backgroundPosition: 'center', | ||
backgroundSize: 'cover', | ||
backgroundImage: "url(" + profilePhotoUrl + ")" | ||
} | ||
}); | ||
} | ||
return React.createElement(Flex, { | ||
mr: 2, | ||
sx: { | ||
bg: isBot ? 'gray' : 'primary', | ||
height: 32, | ||
width: 32, | ||
borderRadius: '50%', | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
color: '#fff' | ||
} | ||
}, isBot ? React.createElement(BotIcon, { | ||
fill: 'background', | ||
height: 16, | ||
width: 16 | ||
}) : name.slice(0, 1).toUpperCase()); | ||
}; | ||
var ChatMessage = function ChatMessage(_ref2) { | ||
var message = _ref2.message, | ||
isMe = _ref2.isMe, | ||
isLastInGroup = _ref2.isLastInGroup, | ||
shouldDisplayTimestamp = _ref2.shouldDisplayTimestamp; | ||
var body = message.body, | ||
created_at = message.created_at, | ||
user = message.user, | ||
type = message.type; | ||
var created = dayjs.utc(created_at); | ||
var timestamp = formatRelativeTime(created); | ||
var isBot = type === 'bot'; | ||
var identifer = isBot ? 'Bot' : getAgentIdentifier(user); | ||
if (isMe) { | ||
return React.createElement(Box, { | ||
pr: 0, | ||
pl: 4, | ||
pb: isLastInGroup ? 3 : 2 | ||
}, React.createElement(Flex, { | ||
sx: { | ||
justifyContent: 'flex-end' | ||
} | ||
}, React.createElement(Box, { | ||
px: '14px', | ||
py: 2, | ||
sx: { | ||
color: 'background', | ||
bg: 'primary', | ||
borderRadius: 4 | ||
} | ||
}, React.createElement(Text, null, body))), shouldDisplayTimestamp && React.createElement(Flex, { | ||
m: 1, | ||
sx: { | ||
justifyContent: 'flex-end' | ||
} | ||
}, React.createElement(Text, { | ||
sx: { | ||
color: 'gray' | ||
} | ||
}, "Sent ", timestamp))); | ||
} | ||
return React.createElement(Box, { | ||
pr: 4, | ||
pl: 0, | ||
pb: isLastInGroup ? 3 : 2 | ||
}, React.createElement(Flex, { | ||
sx: { | ||
justifyContent: 'flex-start', | ||
alignItems: 'center' | ||
} | ||
}, React.createElement(SenderAvatar, { | ||
name: identifer, | ||
user: user, | ||
isBot: isBot | ||
}), React.createElement(Box, { | ||
px: '14px', | ||
py: 2, | ||
sx: { | ||
color: 'text', | ||
bg: 'rgb(245, 245, 245)', | ||
borderRadius: 4, | ||
maxWidth: '80%' | ||
} | ||
}, body)), shouldDisplayTimestamp && React.createElement(Flex, { | ||
m: 1, | ||
sx: { | ||
justifyContent: 'flex-start' | ||
} | ||
}, React.createElement(Text, { | ||
sx: { | ||
color: 'gray' | ||
} | ||
}, identifer, " \xB7 Sent ", timestamp))); | ||
}; | ||
var SendIcon = function SendIcon(_ref) { | ||
var width = _ref.width, | ||
height = _ref.height, | ||
fill = _ref.fill, | ||
className = _ref.className; | ||
return jsx("svg", { | ||
focusable: 'false', | ||
"aria-hidden": 'true', | ||
height: height || 16, | ||
width: width || 16, | ||
viewBox: '0 0 15 16', | ||
className: className, | ||
sx: { | ||
fill: fill || 'black' | ||
} | ||
}, jsx("g", { | ||
transform: 'translate(-24.000000, -12.000000)' | ||
}, jsx("path", { | ||
d: 'M25.4036262,27.3409362 C24.4176893,27.8509036 23.8195834,27.3951055 24.0683403,26.3201996 L25.0887779,21.910776 C25.2131242,21.3734618 25.7510472,20.8884231 26.3078778,20.8254187 L32.503417,20.1244045 C34.151155,19.9379658 34.1569707,19.6389088 32.503417,19.4549971 L26.3078778,18.7659164 C25.7589338,18.7048617 25.2129433,18.217839 25.0887779,17.6798715 L24.0683403,13.2586546 C23.8198614,12.1820783 24.408944,11.7182276 25.4036262,12.2327184 L38.22304,18.8634497 C39.208977,19.373417 39.2177223,20.1957141 38.22304,20.7102049 L25.4036262,27.3409362 Z' | ||
}))); | ||
}; | ||
var DEFAULT_BASE_URL = 'https://app.papercups.io'; | ||
var getWebsocketUrl = function getWebsocketUrl(baseUrl) { | ||
if (baseUrl === void 0) { | ||
baseUrl = DEFAULT_BASE_URL; | ||
} | ||
var _baseUrl$split = baseUrl.split('://'), | ||
protocol = _baseUrl$split[0], | ||
host = _baseUrl$split[1]; | ||
var isHttps = protocol === 'https'; | ||
return (isHttps ? 'wss' : 'ws') + "://" + host + "/socket"; | ||
}; | ||
function now() { | ||
var date = new Date(); | ||
return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds()); | ||
} | ||
var EMPTY_METADATA = {}; | ||
var createNewCustomer = function createNewCustomer(accountId, metadata, baseUrl) { | ||
if (metadata === void 0) { | ||
metadata = EMPTY_METADATA; | ||
} | ||
if (baseUrl === void 0) { | ||
baseUrl = DEFAULT_BASE_URL; | ||
} | ||
try { | ||
return Promise.resolve(request.post(baseUrl + "/api/customers").send({ | ||
customer: _extends({}, metadata, { | ||
account_id: accountId, | ||
first_seen: now(), | ||
last_seen: now() | ||
}) | ||
}).then(function (res) { | ||
return res.body.data; | ||
})); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
var updateCustomerMetadata = function updateCustomerMetadata(customerId, metadata, baseUrl) { | ||
if (metadata === void 0) { | ||
metadata = EMPTY_METADATA; | ||
} | ||
if (baseUrl === void 0) { | ||
baseUrl = DEFAULT_BASE_URL; | ||
} | ||
try { | ||
return Promise.resolve(request.put(baseUrl + "/api/customers/" + customerId + "/metadata").send({ | ||
metadata: metadata | ||
}).then(function (res) { | ||
return res.body.data; | ||
})); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
var createNewConversation = function createNewConversation(accountId, customerId, baseUrl) { | ||
if (baseUrl === void 0) { | ||
baseUrl = DEFAULT_BASE_URL; | ||
} | ||
try { | ||
return Promise.resolve(request.post(baseUrl + "/api/conversations").send({ | ||
conversation: { | ||
account_id: accountId, | ||
customer_id: customerId | ||
} | ||
}).then(function (res) { | ||
return res.body.data; | ||
})); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
var fetchCustomerConversations = function fetchCustomerConversations(customerId, accountId, baseUrl) { | ||
if (baseUrl === void 0) { | ||
baseUrl = DEFAULT_BASE_URL; | ||
} | ||
try { | ||
return Promise.resolve(request.get(baseUrl + "/api/conversations/customer").query({ | ||
customer_id: customerId, | ||
account_id: accountId | ||
}).then(function (res) { | ||
return res.body.data; | ||
})); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
var PREFIX = '__PAPERCUPS__'; | ||
var get = function get(key) { | ||
var result = localStorage.getItem("" + PREFIX + key); | ||
if (!result) { | ||
return null; | ||
} | ||
try { | ||
return JSON.parse(result); | ||
} catch (e) { | ||
return result; | ||
} | ||
}; | ||
var set = function set(key, value) { | ||
localStorage.setItem("" + PREFIX + key, JSON.stringify(value)); | ||
}; | ||
var getCustomerId = function getCustomerId() { | ||
return get('__CUSTOMER_ID__'); | ||
}; | ||
var setCustomerId = function setCustomerId(id) { | ||
return set('__CUSTOMER_ID__', id); | ||
}; | ||
var ChatWindow = /*#__PURE__*/function (_React$Component) { | ||
_inheritsLoose(ChatWindow, _React$Component); | ||
function ChatWindow() { | ||
var _this; | ||
_this = _React$Component.apply(this, arguments) || this; | ||
_this.scrollToEl = null; | ||
_this.state = { | ||
message: '', | ||
messages: [], | ||
customerId: getCustomerId(), | ||
conversationId: null, | ||
isSending: false | ||
}; | ||
_this.getDefaultGreeting = function () { | ||
var greeting = _this.props.greeting; | ||
if (!greeting) { | ||
return []; | ||
} | ||
return [{ | ||
type: 'bot', | ||
body: greeting, | ||
created_at: now().toString() | ||
}]; | ||
}; | ||
_this.fetchLatestConversation = function (customerId) { | ||
try { | ||
if (!customerId) { | ||
_this.setState({ | ||
messages: [].concat(_this.getDefaultGreeting()) | ||
}); | ||
return Promise.resolve(); | ||
} | ||
var _this$props = _this.props, | ||
accountId = _this$props.accountId, | ||
baseUrl = _this$props.baseUrl, | ||
metadata = _this$props.customer; | ||
console.log('Fetching conversations for customer:', customerId); | ||
return Promise.resolve(_catch(function () { | ||
return Promise.resolve(fetchCustomerConversations(customerId, accountId, baseUrl)).then(function (conversations) { | ||
console.log('Found existing conversations:', conversations); | ||
if (!conversations || !conversations.length) { | ||
_this.setState({ | ||
messages: [].concat(_this.getDefaultGreeting()) | ||
}); | ||
return; | ||
} | ||
var latest = conversations[0]; | ||
var conversationId = latest.id, | ||
_latest$messages = latest.messages, | ||
messages = _latest$messages === void 0 ? [] : _latest$messages; | ||
var formattedMessages = messages.sort(function (a, b) { | ||
return +new Date(a.created_at) - +new Date(b.created_at); | ||
}); | ||
_this.setState({ | ||
conversationId: conversationId, | ||
messages: [].concat(_this.getDefaultGreeting(), formattedMessages) | ||
}); | ||
_this.joinConversationChannel(conversationId, customerId); | ||
return Promise.resolve(_this.updateExistingCustomer(customerId, metadata)).then(function () {}); | ||
}); | ||
}, function (err) { | ||
console.log('Error fetching conversations!', err); | ||
})); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
_this.createNewCustomerId = function (accountId) { | ||
try { | ||
var _this$props2 = _this.props, | ||
baseUrl = _this$props2.baseUrl, | ||
metadata = _this$props2.customer; | ||
return Promise.resolve(createNewCustomer(accountId, metadata, baseUrl)).then(function (_ref) { | ||
var customerId = _ref.id; | ||
setCustomerId(customerId); | ||
return customerId; | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
_this.updateExistingCustomer = function (customerId, metadata) { | ||
try { | ||
if (!metadata) { | ||
return Promise.resolve(); | ||
} | ||
var _temp2 = _catch(function () { | ||
var baseUrl = _this.props.baseUrl; | ||
return Promise.resolve(updateCustomerMetadata(customerId, metadata, baseUrl)).then(function () {}); | ||
}, function (err) { | ||
console.log('Error updating customer metadata!', err); | ||
}); | ||
return Promise.resolve(_temp2 && _temp2.then ? _temp2.then(function () {}) : void 0); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
_this.initializeNewConversation = function () { | ||
try { | ||
var _this$props3 = _this.props, | ||
accountId = _this$props3.accountId, | ||
baseUrl = _this$props3.baseUrl; | ||
return Promise.resolve(_this.createNewCustomerId(accountId)).then(function (customerId) { | ||
return Promise.resolve(createNewConversation(accountId, customerId, baseUrl)).then(function (_ref2) { | ||
var conversationId = _ref2.id; | ||
_this.setState({ | ||
customerId: customerId, | ||
conversationId: conversationId | ||
}); | ||
_this.joinConversationChannel(conversationId, customerId); | ||
return { | ||
customerId: customerId, | ||
conversationId: conversationId | ||
}; | ||
}); | ||
}); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
_this.joinConversationChannel = function (conversationId, customerId) { | ||
if (_this.channel && _this.channel.leave) { | ||
_this.channel.leave(); | ||
} | ||
console.log('Joining channel:', conversationId); | ||
_this.channel = _this.socket.channel("conversation:" + conversationId, { | ||
customer_id: customerId | ||
}); | ||
_this.channel.on('shout', function (message) { | ||
_this.handleNewMessage(message); | ||
}); | ||
_this.channel.join().receive('ok', function (res) { | ||
console.log('Joined successfully!', res); | ||
}).receive('error', function (err) { | ||
console.log('Unable to join!', err); | ||
}); | ||
_this.scrollToEl.scrollIntoView(); | ||
}; | ||
_this.handleNewMessage = function (message) { | ||
_this.setState({ | ||
messages: [].concat(_this.state.messages, [message]) | ||
}, function () { | ||
_this.scrollToEl.scrollIntoView(); | ||
}); | ||
}; | ||
_this.handleMessageChange = function (e) { | ||
_this.setState({ | ||
message: e.target.value | ||
}); | ||
}; | ||
_this.handleKeyDown = function (e) { | ||
if (e.key === 'Enter') { | ||
_this.handleSendMessage(e); | ||
} | ||
}; | ||
_this.handleSendMessage = function (e) { | ||
try { | ||
var _temp5 = function _temp5() { | ||
if (!_this.channel) { | ||
_this.setState({ | ||
isSending: false | ||
}); | ||
return; | ||
} | ||
_this.channel.push('shout', { | ||
body: message, | ||
customer_id: _this.state.customerId | ||
}); | ||
_this.setState({ | ||
message: '', | ||
isSending: false | ||
}); | ||
}; | ||
e && e.preventDefault(); | ||
var _this$state = _this.state, | ||
message = _this$state.message, | ||
customerId = _this$state.customerId, | ||
conversationId = _this$state.conversationId, | ||
isSending = _this$state.isSending; | ||
if (isSending || !message || message.trim().length === 0) { | ||
return Promise.resolve(); | ||
} | ||
_this.setState({ | ||
isSending: true | ||
}); | ||
var _temp6 = function () { | ||
if (!customerId || !conversationId) { | ||
return Promise.resolve(_this.initializeNewConversation()).then(function () {}); | ||
} | ||
}(); | ||
return Promise.resolve(_temp6 && _temp6.then ? _temp6.then(_temp5) : _temp5(_temp6)); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} | ||
}; | ||
return _this; | ||
} | ||
var _proto = ChatWindow.prototype; | ||
_proto.componentDidMount = function componentDidMount() { | ||
var websocketUrl = getWebsocketUrl(this.props.baseUrl); | ||
this.socket = new Socket(websocketUrl); | ||
this.socket.connect(); | ||
this.fetchLatestConversation(this.state.customerId); | ||
}; | ||
_proto.componentWillUnmount = function componentWillUnmount() { | ||
this.channel && this.channel.leave(); | ||
}; | ||
_proto.render = function render() { | ||
var _this2 = this; | ||
var _this$props4 = this.props, | ||
_this$props4$title = _this$props4.title, | ||
title = _this$props4$title === void 0 ? 'Welcome!' : _this$props4$title, | ||
_this$props4$subtitle = _this$props4.subtitle, | ||
subtitle = _this$props4$subtitle === void 0 ? 'How can we help you?' : _this$props4$subtitle; | ||
var _this$state2 = this.state, | ||
customerId = _this$state2.customerId, | ||
message = _this$state2.message, | ||
_this$state2$messages = _this$state2.messages, | ||
messages = _this$state2$messages === void 0 ? [] : _this$state2$messages, | ||
isSending = _this$state2.isSending; | ||
return React.createElement(Flex, { | ||
sx: { | ||
bg: 'background', | ||
flexDirection: 'column', | ||
height: '100%', | ||
width: '100%' | ||
} | ||
}, React.createElement(Box, { | ||
py: 3, | ||
px: 4, | ||
sx: { | ||
bg: 'primary' | ||
} | ||
}, React.createElement(Heading, { | ||
as: 'h2', | ||
className: 'Papercups-heading', | ||
sx: { | ||
color: 'background', | ||
my: 1 | ||
} | ||
}, title), React.createElement(Text, { | ||
sx: { | ||
color: 'offset' | ||
} | ||
}, subtitle)), React.createElement(Box, { | ||
p: 3, | ||
sx: { | ||
flex: 1, | ||
boxShadow: 'rgba(0, 0, 0, 0.2) 0px 21px 4px -20px inset', | ||
overflowY: 'scroll' | ||
} | ||
}, messages.map(function (msg, key) { | ||
var next = messages[key + 1]; | ||
var isLastInGroup = next ? msg.customer_id !== next.customer_id : true; | ||
var shouldDisplayTimestamp = key === messages.length - 1; | ||
var isMe = msg.customer_id === customerId; | ||
return React.createElement(motion.div, { | ||
key: key, | ||
initial: { | ||
opacity: 0, | ||
x: isMe ? 2 : -2 | ||
}, | ||
animate: { | ||
opacity: 1, | ||
x: 0 | ||
}, | ||
transition: { | ||
duration: 0.2, | ||
ease: 'easeIn' | ||
} | ||
}, React.createElement(ChatMessage, { | ||
key: key, | ||
message: msg, | ||
isMe: isMe, | ||
isLastInGroup: isLastInGroup, | ||
shouldDisplayTimestamp: shouldDisplayTimestamp | ||
})); | ||
}), React.createElement("div", { | ||
ref: function ref(el) { | ||
return _this2.scrollToEl = el; | ||
} | ||
})), React.createElement(Box, { | ||
p: 2, | ||
sx: { | ||
borderTop: '1px solid rgb(230, 230, 230)', | ||
boxShadow: 'rgba(0, 0, 0, 0.1) 0px 0px 100px 0px' | ||
} | ||
}, React.createElement(Flex, { | ||
sx: { | ||
alignItems: 'center' | ||
} | ||
}, React.createElement(Box, { | ||
mr: 3, | ||
sx: { | ||
flex: 1 | ||
} | ||
}, React.createElement(Textarea, { | ||
sx: { | ||
fontFamily: 'body', | ||
color: 'input', | ||
variant: 'styles.textarea.transparent' | ||
}, | ||
className: 'TextArea--transparent', | ||
placeholder: 'Start typing...', | ||
rows: 1, | ||
autoFocus: true, | ||
value: message, | ||
onKeyDown: this.handleKeyDown, | ||
onChange: this.handleMessageChange | ||
})), React.createElement(Box, { | ||
pl: 3 | ||
}, React.createElement(Button, { | ||
variant: 'primary', | ||
type: 'submit', | ||
disabled: isSending, | ||
onClick: this.handleSendMessage, | ||
sx: { | ||
display: 'flex', | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
borderRadius: '50%', | ||
height: '36px', | ||
width: '36px', | ||
padding: 0 | ||
} | ||
}, React.createElement(SendIcon, { | ||
width: 16, | ||
height: 16, | ||
fill: 'background' | ||
}))))), React.createElement("img", { | ||
src: 'https://papercups.s3.us-east-2.amazonaws.com/papercups-logo.svg', | ||
width: '0', | ||
height: '0' | ||
})); | ||
}; | ||
return ChatWindow; | ||
}(React.Component); | ||
var Path = function Path(props) { | ||
@@ -1045,2 +296,26 @@ return React.createElement(motion.path, Object.assign({ | ||
var IFRAME_URL = 'https://chat-window.vercel.app'; | ||
var setup = function setup(w, handlers) { | ||
var cb = function cb(msg) { | ||
if (msg.origin !== IFRAME_URL) { | ||
return; | ||
} | ||
handlers(msg); | ||
}; | ||
if (w.addEventListener) { | ||
w.addEventListener('message', cb); | ||
return function () { | ||
return w.removeEventListener('message', cb); | ||
}; | ||
} else { | ||
w.attachEvent('onmessage', cb); | ||
return function () { | ||
return w.detachEvent('message', cb); | ||
}; | ||
} | ||
}; | ||
var EmbeddableWidget = function EmbeddableWidget(_ref) { | ||
@@ -1057,6 +332,15 @@ var accountId = _ref.accountId, | ||
var _React$useState = React.useState(defaultIsOpen), | ||
var _React$useState = React.useState(false), | ||
isOpen = _React$useState[0], | ||
setIsOpen = _React$useState[1]; | ||
var iframeRef = React.useRef(null); | ||
var query = qs.stringify({ | ||
accountId: accountId, | ||
title: title, | ||
subtitle: subtitle, | ||
primaryColor: primaryColor, | ||
baseUrl: baseUrl, | ||
greeting: greeting | ||
}); | ||
var theme = getThemeConfig({ | ||
@@ -1066,2 +350,56 @@ primary: primaryColor | ||
var send = function send(event, payload) { | ||
console.log('Sending from parent:', { | ||
event: event, | ||
payload: payload | ||
}); | ||
var el = iframeRef.current; | ||
el.contentWindow.postMessage({ | ||
event: event, | ||
payload: payload | ||
}, '*'); | ||
}; | ||
var handleChatLoaded = function handleChatLoaded() { | ||
if (defaultIsOpen) { | ||
setIsOpen(true); | ||
} | ||
return send('papercups:ping'); | ||
}; | ||
var sendCustomerUpdate = function sendCustomerUpdate(payload) { | ||
var customerId = payload.customerId; | ||
return send('customer:update', { | ||
customerId: customerId, | ||
metadata: customer | ||
}); | ||
}; | ||
var handlers = function handlers(msg) { | ||
console.log('Handling in parent:', msg.data); | ||
var _msg$data = msg.data, | ||
event = _msg$data.event, | ||
_msg$data$payload = _msg$data.payload, | ||
payload = _msg$data$payload === void 0 ? {} : _msg$data$payload; | ||
switch (event) { | ||
case 'chat:loaded': | ||
return handleChatLoaded(); | ||
case 'conversation:join': | ||
return sendCustomerUpdate(payload); | ||
default: | ||
return null; | ||
} | ||
}; | ||
React.useEffect(function () { | ||
var unsubscribe = setup(window, handlers); | ||
return function () { | ||
return unsubscribe(); | ||
}; | ||
}, []); | ||
var handleToggleOpen = function handleToggleOpen() { | ||
@@ -1073,12 +411,16 @@ return setIsOpen(!isOpen); | ||
theme: theme | ||
}, isOpen && jsx(motion.div, { | ||
}, jsx(motion.iframe, { | ||
ref: iframeRef, | ||
className: 'Papercups-chatWindowContainer', | ||
initial: { | ||
opacity: 0, | ||
y: 4 | ||
animate: isOpen ? 'open' : 'closed', | ||
variants: { | ||
closed: { | ||
opacity: 0, | ||
y: 4 | ||
}, | ||
open: { | ||
opacity: 1, | ||
y: 0 | ||
} | ||
}, | ||
animate: { | ||
opacity: 1, | ||
y: 0 | ||
}, | ||
transition: { | ||
@@ -1088,13 +430,12 @@ duration: 0.2, | ||
}, | ||
src: IFRAME_URL + "?" + query, | ||
style: isOpen ? {} : { | ||
bottom: -9999 | ||
}, | ||
sx: { | ||
border: 'none', | ||
bg: 'background', | ||
variant: 'styles.WidgetContainer' | ||
} | ||
}, jsx(ChatWindow, { | ||
title: title, | ||
subtitle: subtitle, | ||
accountId: accountId, | ||
greeting: greeting, | ||
customer: customer, | ||
baseUrl: baseUrl | ||
})), jsx(motion.div, { | ||
}, "Loading..."), jsx(motion.div, { | ||
className: 'Papercups-toggleButtonContainer', | ||
@@ -1101,0 +442,0 @@ initial: false, |
{ | ||
"name": "@papercups-io/chat-widget", | ||
"version": "1.0.15", | ||
"version": "1.0.16-beta.0", | ||
"description": "Papercups chat widget", | ||
@@ -71,2 +71,3 @@ "author": "reichert621", | ||
"phoenix": "^1.5.3", | ||
"query-string": "^6.13.1", | ||
"superagent": "^5.3.1", | ||
@@ -73,0 +74,0 @@ "theme-ui": "^0.3.1", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
103534
8
1930
2
+ Addedquery-string@^6.13.1
+ Addeddecode-uri-component@0.2.2(transitive)
+ Addedfilter-obj@1.1.0(transitive)
+ Addedquery-string@6.14.1(transitive)
+ Addedsplit-on-first@1.1.0(transitive)
+ Addedstrict-uri-encode@2.0.0(transitive)