diff --git a/app/locales.js b/app/locales.js index bce2272b..7998bc24 100644 --- a/app/locales.js +++ b/app/locales.js @@ -192,7 +192,6 @@ Ext.define('Ngcp.csc.locales', { sp: 'all calls' } }, - filters: { search: { en: 'SEARCH', @@ -237,7 +236,6 @@ Ext.define('Ngcp.csc.locales', { sp: 'reset filter' } }, - voicemails: { title: { en: 'Your voicemail recordings.', @@ -305,7 +303,7 @@ Ext.define('Ngcp.csc.locales', { fr: 'Send notification of new messages to the following e-mail adddress', sp: 'Send notification of new messages to the following e-mail adddress' }, - attach_recording:{ + attach_recording: { en: 'attach recording', it: 'attach recording', de: 'attach recording', @@ -336,6 +334,31 @@ Ext.define('Ngcp.csc.locales', { } }, + chat: { + title: { + en: 'Team', + it: 'Team', + de: 'Team', + fr: 'Team', + sp: 'Team' + }, + msg_box:{ + empty_text:{ + en: 'Type a message', + it: 'Type a message', + de: 'Type a message', + fr: 'Type a message', + sp: 'Type a message' + } + }, + start_conversation: { + en: 'You can start a private conversation with {0} here.', + it: 'You can start a private conversation with {0} here.', + de: 'You can start a private conversation with {0} here.', + fr: 'You can start a private conversation with {0} here.', + sp: 'You can start a private conversation with {0} here.' + } + }, common: { today: { en: 'Today', @@ -372,14 +395,14 @@ Ext.define('Ngcp.csc.locales', { fr: 'no', sp: 'no' }, - delete:{ + delete: { en: 'Delete', it: 'Delete', de: 'Delete', fr: 'Delete', sp: 'Delete' }, - listen:{ + listen: { en: 'Listen', it: 'Listen', de: 'Listen', @@ -413,7 +436,15 @@ Ext.define('Ngcp.csc.locales', { de: 'Successfully removed.', fr: 'Successfully removed.', sp: 'Successfully removed.' + }, + submit: { + en: 'submit', + it: 'submit', + de: 'submit', + fr: 'submit', + sp: 'submit' } + } } }) diff --git a/app/model/ChatList.js b/app/model/ChatList.js new file mode 100644 index 00000000..e3db42cf --- /dev/null +++ b/app/model/ChatList.js @@ -0,0 +1,16 @@ +Ext.define('NgcpCsc.model.ChatList', { + extend: 'Ext.data.Model', + fields: [{ + type: 'int', + name: 'id' + }, { + type: 'string', + name: 'name' + }, { + type: 'string', + name: 'thumbnail' + }, { + type: 'boolean', + name: 'online' + }] +}); diff --git a/app/model/ChatNotification.js b/app/model/ChatNotification.js new file mode 100644 index 00000000..d763e25c --- /dev/null +++ b/app/model/ChatNotification.js @@ -0,0 +1,20 @@ +Ext.define('NgcpCsc.model.ChatNotification', { + extend: 'Ext.data.Model', + fields: [{ + name: '_id' + }, { + name: 'parent_id' + }, { + name: 'name' + }, { + name: 'source' + }, { + name: 'date' + }, { + name: 'isActive' + }, { + name: 'time' + }, { + name: 'content' + }] +}); diff --git a/app/store/Chat.js b/app/store/Chat.js new file mode 100644 index 00000000..52bcfef6 --- /dev/null +++ b/app/store/Chat.js @@ -0,0 +1,18 @@ +Ext.define('NgcpCsc.store.Chat', { + extend: 'Ext.data.Store', + + storeId: 'Chat', + + model: 'NgcpCsc.model.ChatNotification', + + autoLoad: true, + + proxy: { + type: 'ajax', + url: '/resources/data/chat.json', + reader: { + type: 'json', + rootProperty: 'data' + } + } +}); diff --git a/app/store/ChatList.js b/app/store/ChatList.js new file mode 100644 index 00000000..d76234b9 --- /dev/null +++ b/app/store/ChatList.js @@ -0,0 +1,25 @@ +Ext.define('NgcpCsc.store.ChatList', { + extend: 'Ext.data.Store', + + alias: 'store.chatlist', + + storeId: 'ChatList', + + model: 'NgcpCsc.model.ChatList', + + autoLoad: true, + + proxy: { + type: 'ajax', + url: '/resources/data/chatlist.json', + reader: { + type: 'json', + rootProperty: 'data' + } + }, + + sorters: { + direction: 'DESC', + property: 'online' + } +}); diff --git a/app/store/NavigationTree.js b/app/store/NavigationTree.js index ce15b9d6..79051d8a 100644 --- a/app/store/NavigationTree.js +++ b/app/store/NavigationTree.js @@ -27,6 +27,12 @@ Ext.define('NgcpCsc.store.NavigationTree', { viewType: 'voicemails', routeId: 'voicebox', leaf: true + }, { + text: 'Chat', + iconCls: 'x-fa fa-wechat', + viewType: 'chat', + routeId: 'chat', + leaf: true }, { text: 'Address book', iconCls: 'x-fa fa-book', diff --git a/classic/sass/src/view/main/Main.scss b/classic/sass/src/view/main/Main.scss index 603b10c9..c8a903c3 100644 --- a/classic/sass/src/view/main/Main.scss +++ b/classic/sass/src/view/main/Main.scss @@ -334,3 +334,6 @@ .red-txt { color: red; } +.x-panel-bodyWrap { + background-color: white; +} diff --git a/classic/sass/src/view/pages/chat/Chat.scss b/classic/sass/src/view/pages/chat/Chat.scss new file mode 100644 index 00000000..65b31f02 --- /dev/null +++ b/classic/sass/src/view/pages/chat/Chat.scss @@ -0,0 +1,295 @@ +.user-notifications { + padding: 15px; + background: #fff; + + .x-view-item-focused { + outline: 0 !important; + } +} + +.timeline-item { + .line-wrap { + position: relative; + + &:before { + content: ""; + position: absolute; + width: 50px; + height: 100%; + border-right: solid 2px $base-color; + left: 0; + top: 70px; + } + } + + .profile-pic-wrap { + width: 100px; + float: left; + font-size: 10px; + text-align: center; + position: relative; + background: $lightest-color; + padding: 5px 0; + + img { + height: 46px; + width: 46px; + @include border-radius($circle-border-radius); + } + } + + .contents-wrap { + margin: 0 0 15px 110px; + border: 1px solid $base-border-color; + position: relative; + padding: 15px; + white-space: normal; + + &:after, + &:before { + content: ""; + display: block; + position: absolute; + width: 0; + height: 0; + left: 0; + top: 0; + } + + &:before { + border-top: 9px solid transparent; + border-bottom: 9px solid transparent; + border-right: 9px solid $base-border-color; + margin: 15px 0 0 -9px; + } + + &:after { + border-top: 9px solid transparent; + border-bottom: 9px solid transparent; + border-right: 9px solid $lightest-color; + margin: 15px 0 0 -8px; + } + + .followed-by, + .shared-by { + font-size: 12px; + line-height: 18px; + margin-bottom: 10px; + + a { + color: $base-color; + text-decoration: none; + font-weight: bold; + } + } + + .shared-img { + width: 100%; + display: block; + } + + .job-meeting a { + color: $base-color; + text-decoration: none; + font-weight: bold; + margin-bottom: 10px; + display: block; + } + } + + .article-comment { + border-left: 5px solid $base-border-color; + padding: 10px 20px; + + span { + margin-right: 10px; + color: $article-comment-color; + } + } + + .followed-by { + img { + height: 32px; + width: 32px; + @include border-radius($circle-border-radius); + margin-right: 5px; + float: left; + } + + .followed-by-inner { + margin-left: 40px; + padding: 5px 0; + } + } +} + +.comments { + margin-bottom: 10px; + cursor: pointer; + + img.profile-icon { + height: 50px; + width: 50px; + border: 2px solid $base-border-color; + @include border-radius($circle-border-radius); + float: left; + } + + h4 { + font-size: 14px; + float: left; + + span { + margin-left: 8px; + font-size: 18px; + position: relative; + top: 2px; + } + } + + .from-now { + float: right; + + span { + margin-right: 5px; + font-size: 16px; + position: relative; + top: 1px; + } + } + + .content-wrap { + margin-left: 65px; + + .content { + margin-bottom: 15px; + white-space: normal; + clear: both; + } + } + + &.sub-comments { + margin: 0 0 10px 60px; + + .like-comment-btn-wrap { + border-bottom: none; + } + } +} + +.like-comment-btn-wrap { + text-align: right; + padding-bottom: 15px; + + button { + margin-left: 15px; + height: 30px; + width: 30px; + background-color: $like-comment-btn-background-color; + font-size: 14px; + color: $color; + border: none; + cursor: pointer; + } +} + +.menu-item-common { + padding-right: 20px; + position: absolute; + font-family: FontAwesome; + right: 0; +} + +.navigation-email { + // @include box-shadow(0px,2px,8px,0px,rgba(0,0,0,.15)); + @include box-shadow(0, 1px, 2px, 0, rgba(0,0,0,0.2)); + + &.x-menu-default { + border-width: 0; + } + + .x-menu-header { + line-height: 20px; + background-color: $lightest-color; + padding: 22px 15px; + border-bottom: 1px solid #ccc !important; + + .x-title-icon-wrap { + width: 40px; + padding-right: 28px; + text-align: left; + } + + .x-title-text { + font-size: 16px; + } + } + + .x-menu-item-icon-default { + padding-top: 10px; + padding-left: 12px; + font-size: 16px; + line-height: 24px; + } + + .x-menu-item { + line-height: 50px; + + .x-menu-item-text-default.x-menu-item-indent-no-separator { + margin-left: 56px; + } + + &.online-user { + .x-menu-item-text-default.x-menu-item-indent-no-separator { + margin-left: 18px; + } + + .x-menu-item-link:after { + color: $online-menu-item-color; + content: "\f111"; + @extend .menu-item-common; + } + + &.x-menu-item-active { + .x-menu-item-link:after { + color: white; + } + } + } + + &.offline-user { + .x-menu-item-link:after { + color: $offline-menu-item-color; + content: "\f111"; + @extend .menu-item-common; + } + + .x-menu-item-text-default.x-menu-item-indent-no-separator { + margin-left: 20px; + } + } + + .x-menu-item-link:after { + @extend .menu-item-common; + color: $default-menu-item-color; + content: "\f105"; + } + } +} + +.new-message-cont { + margin-left: 203px; +} + +.submit-new-message { + float: right; + top: 0 !important; +} + +.hide-pm { + display: none; +} + +.private-conversation-text { + padding: 30px; +} diff --git a/classic/sass/var/login/Login.scss b/classic/sass/var/login/Login.scss deleted file mode 100644 index 07ad1c2a..00000000 --- a/classic/sass/var/login/Login.scss +++ /dev/null @@ -1 +0,0 @@ -$dialog-trigger-color : #e5e5e5; diff --git a/classic/sass/var/view/main/Main.scss b/classic/sass/var/view/main/Main.scss index c97c1c65..25f0b9ea 100644 --- a/classic/sass/var/view/main/Main.scss +++ b/classic/sass/var/view/main/Main.scss @@ -14,3 +14,8 @@ $social-envelope-btn-background: #7754aa; // custom vars $console-msg-font-size: 16px; + +$default-menu-item-color: #858585; +$online-menu-item-color: #86c747; +$offline-menu-item-color: #9e9f9f; +$menu-item-focus-color: #88bf4c; diff --git a/classic/sass/var/pages/BlankPage.scss b/classic/sass/var/view/pages/BlankPage.scss similarity index 100% rename from classic/sass/var/pages/BlankPage.scss rename to classic/sass/var/view/pages/BlankPage.scss diff --git a/classic/sass/var/view/pages/chat/Chat.scss b/classic/sass/var/view/pages/chat/Chat.scss new file mode 100644 index 00000000..3e496546 --- /dev/null +++ b/classic/sass/var/view/pages/chat/Chat.scss @@ -0,0 +1,3 @@ +$like-comment-btn-background-color: #f4f4f4; +$like-comment-btn-color: #979797; +$article-comment-color: #eee; diff --git a/classic/src/Application.js b/classic/src/Application.js index 2c74edc9..4dc62857 100644 --- a/classic/src/Application.js +++ b/classic/src/Application.js @@ -19,6 +19,8 @@ Ext.define('NgcpCsc.Application', { 'NavigationTree', 'Calls', 'VoiceMails', + 'Chat', + 'ChatList', 'Languages' ], diff --git a/classic/src/view/main/Main.js b/classic/src/view/main/Main.js index 38577f3c..35c87981 100644 --- a/classic/src/view/main/Main.js +++ b/classic/src/view/main/Main.js @@ -78,6 +78,7 @@ Ext.define('NgcpCsc.view.main.Main', { id: 'main-view-detail-wrap', reference: 'mainContainerWrap', flex: 1, + scrollable: false, items: [{ xtype: 'treelist', reference: 'navigationTreeList', @@ -96,9 +97,9 @@ Ext.define('NgcpCsc.view.main.Main', { reference: 'mainCardPanel', cls: 'sencha-dash-right-main-container', itemId: 'contentPanel', + height: 500, layout: { - type: 'card', - anchor: '100%' + type: 'card' } }] }] diff --git a/classic/src/view/pages/chat/Chat.js b/classic/src/view/pages/chat/Chat.js new file mode 100644 index 00000000..cbbd5f55 --- /dev/null +++ b/classic/src/view/pages/chat/Chat.js @@ -0,0 +1,60 @@ +Ext.define('NgcpCsc.view.pages.chat.Chat', { + extend: 'Ext.panel.Panel', + + xtype: 'chat', + + viewModel: 'chat', + + controller: 'chat', + + layout: 'hbox', + + items: [{ + xtype: 'chatlist', + width: 200, + padding: '10 20 20', + height: '100%' + }, { + xtype: 'tabpanel', + width: '90%', + height: '100%', + items: [{ + title: Ngcp.csc.locales.chat.title[localStorage.getItem('languageSelected')], + xtype: 'chat-notifications', + id: 'chat-notifications', + scrollable: true, + bind: { + store: '{notifications}' + } + }] + }], + + dockedItems: [{ + xtype: 'toolbar', + cls: 'new-message-cont', + fixed: true, + padding: '0 0 10 0', + dock: 'bottom', + items: [{ + xtype: 'textarea', + bind: { + value: '{new_message}' + }, + cls: 'new-message', + name: 'new-message', + enableKeyEvents: true, + height: 100, + width: '95%', + listeners: { + keypress: 'onPressEnter' + }, + emptyText: Ngcp.csc.locales.chat.msg_box.empty_text[localStorage.getItem('languageSelected')] + }, { + xtype: 'button', + cls: 'submit-new-message', + text: Ngcp.csc.locales.common.submit[localStorage.getItem('languageSelected')], + handler: 'onPressSubmitBtn' + }] + }] + +}); diff --git a/classic/src/view/pages/chat/ChatController.js b/classic/src/view/pages/chat/ChatController.js new file mode 100644 index 00000000..cbf98ecb --- /dev/null +++ b/classic/src/view/pages/chat/ChatController.js @@ -0,0 +1,80 @@ +Ext.define('NgcpCsc.view.pages.chat.ChatController', { + extend: 'Ext.app.ViewController', + + alias: 'controller.chat', + + listen: { + controller: { + '#chatlist': { + openpmtab: 'openPM' + } + } + }, + + onPressEnter: function(field, e) { + if (e.getKey() == e.ENTER) { + e.preventDefault(); + this.submitMessage(); + } + }, + + onPressSubmitBtn: function(field, e) { + this.submitMessage(); + }, + + submitMessage: function(msg, user) { + var message = msg || this.getViewModel().get('new_message'); + if (message.length < 1) + return; + var chatStore = this.getView().down('tabpanel').getActiveTab().getStore('notifications'); + var lastMsg = chatStore.getAt(chatStore.getCount() - 1) || this.getViewModel().getStore('notifications').findRecord('id', this.getView().down('tabpanel').getActiveTab().name); + var date = new Date(); + var minutes = date.getMinutes(); + var hour = date.getHours(); + var day = date.getDate(); + var month = date.getMonth() + 1; + var messageModel = Ext.create('NgcpCsc.model.ChatNotification', { + /// "id": (user) ? user.get('id') : 0, + "name": (user) ? user.get('name') : localStorage.getItem('username'), + "date": Ext.String.format("{0}.{1}", day, month), + "isActive": true, + "time": Ext.String.format("{0}:{1}", hour, minutes), + "thumbnail": (user) ? user.get('thumbnail') : "resources/images/user-profile/2.png", + "content": message + }); + chatStore.add(messageModel); + this.clearMsg(); + this.focusLastMsg(); + }, + + clearMsg: function() { + this.getView().down('[name=new-message]').reset(); + }, + + focusLastMsg: function(rec) { + var chatCmp = this.getView().down('tabpanel').getActiveTab(); + chatCmp.scrollTo(0, chatCmp.getEl().dom.scrollHeight); + }, + + openPM: function(item, rec) { + var tab = this.getView().down('[name=' + rec.get('id') + ']'); + if (rec.get('name') == 'administrator') // hardcoded administrator + return; + if (!tab) { + tab = this.getView().down('tabpanel').add({ + xtype: 'chat-notifications', + title: rec.get('name'), + closable: true, + scrollable: true, + cls: 'private-conversation-text', + deferEmptyText: false, + emptyText: Ext.String.format(Ngcp.csc.locales.chat.start_conversation[localStorage.getItem('languageSelected')], rec.get('name')), + name: rec.get('id'), + store: Ext.create('Ext.data.Store', { + model: 'NgcpCsc.model.ChatNotification' + }) + }); + } + this.getView().down('tabpanel').setActiveTab(tab); + } +}); diff --git a/classic/src/view/pages/chat/ChatList.js b/classic/src/view/pages/chat/ChatList.js new file mode 100644 index 00000000..c1edfcd7 --- /dev/null +++ b/classic/src/view/pages/chat/ChatList.js @@ -0,0 +1,23 @@ +Ext.define('NgcpCsc.view.pages.chat.ChatList', { + extend: 'Ext.menu.Menu', + + alias: 'widget.chatlist', + + viewModel: { + type: 'chatlist' + }, + + controller: 'chatlist', + + title: Ngcp.csc.locales.chat.title[localStorage.getItem('languageSelected')], + + cls: 'navigation-email', + + iconCls: 'x-fa fa-group', + + floating: false, + + listeners: { + click: 'itemListClicked' + } +}); diff --git a/classic/src/view/pages/chat/ChatListController.js b/classic/src/view/pages/chat/ChatListController.js new file mode 100644 index 00000000..d7410d4e --- /dev/null +++ b/classic/src/view/pages/chat/ChatListController.js @@ -0,0 +1,51 @@ +Ext.define('NgcpCsc.view.pages.chat.ChatListController', { + extend: 'Ext.app.ViewController', + + alias: 'controller.chatlist', + + id: 'chatlist', // needed as reference in ChatController listeners + + init: function() { + var me = this, + friendsStore = me.getViewModel().getStore('friends'); + + //Trigger local sorting once new data is available + friendsStore.on('load', function(store) { + store.sort(); + }); + + //Sort locally and then update menu + friendsStore.on('sort', function(store) { + me.mutateData(store, store.getRange()); + }); + + me.callParent(arguments); + }, + + mutateData: function(store, records) { + var view = this.getView(), + arr = [], + len = records.length, + i; + + for (i = 0; i < len; i++) { + arr.push({ + xtype: 'menuitem', + uId: records[i].get('id'), + text: records[i].get('name'), + cls: 'font-icon ' + (records[i].get('online') ? 'online-user' : 'offline-user') + }); + } + + Ext.suspendLayouts(); + view.removeAll(true); + view.add(arr); + Ext.resumeLayouts(true); + }, + + itemListClicked: function(menu, item) { + var selectedUser = Ext.getStore('ChatList').findRecord('id', item.uId, 0, false, true, true); + if (selectedUser && selectedUser.get('online')) + this.fireEvent('openpmtab', null, selectedUser); + } +}); diff --git a/classic/src/view/pages/chat/ChatListModel.js b/classic/src/view/pages/chat/ChatListModel.js new file mode 100644 index 00000000..0c72e5f7 --- /dev/null +++ b/classic/src/view/pages/chat/ChatListModel.js @@ -0,0 +1,15 @@ +Ext.define('NgcpCsc.view.pages.chat.ChatListModel', { + extend: 'Ext.app.ViewModel', + + alias: 'viewmodel.chatlist', + + stores: { + friends: { + //Store reference + type: 'chatlist', + + //Auto load + autoLoad: true + } + } +}); diff --git a/classic/src/view/pages/chat/ChatModel.js b/classic/src/view/pages/chat/ChatModel.js new file mode 100644 index 00000000..932dee6f --- /dev/null +++ b/classic/src/view/pages/chat/ChatModel.js @@ -0,0 +1,11 @@ +Ext.define('NgcpCsc.view.pages.chat.ChatModel', { + extend: 'Ext.app.ViewModel', + alias: 'viewmodel.chat', + data: { + new_message:'' + }, + formulas: {}, + stores: { + notifications: 'Chat' + } +}); diff --git a/classic/src/view/pages/chat/ChatNotifications.js b/classic/src/view/pages/chat/ChatNotifications.js new file mode 100644 index 00000000..ade8434d --- /dev/null +++ b/classic/src/view/pages/chat/ChatNotifications.js @@ -0,0 +1,28 @@ +Ext.define('NgcpCsc.view.pages.chat.ChatNotifications', { + extend: 'Ext.DataView', + xtype: 'chat-notifications', + + cls: 'user-notifications', + + scrollable: false, + + listeners: { + itemclick: 'openPM' + }, + + itemTpl: [ + "
", + "", + "
", + "
", + "

{name}

", + "{time} {date}", + "", + "
", + "
{content}
", + "
", + "
" + ] +}); diff --git a/resources/data/chat.json b/resources/data/chat.json new file mode 100644 index 00000000..1f4df66d --- /dev/null +++ b/resources/data/chat.json @@ -0,0 +1,27 @@ +{ + "data": [{ + "id": 840, + "name": "Jil Sanchez", + "date": "10/27/2016", + "isActive": true, + "time": "13:42", + "thumbnail": "resources/images/user-profile/9.png", + "content": "There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don\'t look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn\'t anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc." + }, { + "id": 252, + "name": "Ben Wright", + "date": "10/27/2010", + "isActive": true, + "time": "14:03", + "thumbnail": "resources/images/user-profile/10.png", + "content": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." + }, { + "id": 162, + "name": "Allen Morris", + "date": "10/27/2016", + "isActive": true, + "time": "4:57", + "thumbnail": "resources/images/user-profile/11.png", + "content": "There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don\'t look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn\'t anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc." + }] +} diff --git a/resources/data/chatlist.json b/resources/data/chatlist.json new file mode 100644 index 00000000..98935195 --- /dev/null +++ b/resources/data/chatlist.json @@ -0,0 +1,27 @@ +{ + "data": [{ + "id": 840, + "online": true, + "name": "Jil Sanchez" + }, { + "id": 1, + "online": false, + "name": "Oneill Franklin" + }, { + "id": 3, + "online": false, + "name": "Branch Allison" + }, { + "id": 252, + "online": true, + "name": "Ben Wright" + }, { + "id": 162, + "online": true, + "name": "Allen Morris" + }, { + "id": 5, + "online": false, + "name": "Suzette Powell" + }] +}