Browse Source

Merge branch 'master' into dev-vj

Vijayakrishnan Krishnan 5 years ago
parent
commit
f15af4bc68

+ 31 - 0
app/Http/Controllers/AppSessionController.php

@@ -91,4 +91,35 @@ class AppSessionController extends Controller
         return redirect($request->input('_success'));
 
     }
+
+    public function postToAPIAjax(Request $request) {
+
+        session()->remove('message');
+
+        // call java api
+
+        $data = [];
+        $fields = $request->all();
+        foreach ($fields as $key => $value) {
+            if($key[0] !== '_') {
+                $data[$key] = $value;
+            }
+        }
+
+        if(!isset($data['uid']) && isset($fields['_uid'])) {
+            $data['uid'] = $fields['_uid'];
+        }
+
+        $url = env('BACKEND_URL', 'http://localhost:8080') . $request->input('_api');
+        // dd($url);
+
+        $response = Http::asForm()
+            ->withHeaders(['sessionKey'=>$request->cookie('sessionKey')])
+            ->post($url, $data)
+            ->json();
+
+        // dd($response);
+
+        return response()->json($response);
+    }
 }

+ 6 - 1
app/Http/Controllers/ClientController.php

@@ -17,6 +17,11 @@ class ClientController extends Controller
 
 	// GET /care_months
 	public function entrance(Request $request, Lobby $lobby) {
+        if (!$lobby->id) {
+            \abort(404);
+            return;
+        }
+
         $sessionKey = Cookie::get('sessionKey');
 
         $lobbyModel = new ClientLobbyModel($lobby);
@@ -49,6 +54,6 @@ class ClientController extends Controller
         if ($session->meetingParticipant && $session->meetingParticipant->meeting->lobby_id === $lobby->id) {
             $meeting = new MeetingModel($session->meetingParticipant->meeting);
         }
-        return view('client/index',compact('lobbyModel','meeting'));
+        return view('client/index',compact('lobbyModel','meeting','sessionKey'));
 	}
 }

+ 1 - 1
app/Http/Controllers/MeetingCenterController.php

@@ -24,7 +24,7 @@ class MeetingCenterController extends Controller
         $appSession = AppSession::where("session_key",$sessionKey)->first();
         $user = new ProModel($appSession->pro);
         $meeting = null;
-        if ($appSession->meetingParticipant) {
+        if ($appSession->is_currently_meeting_participant) {
             $meeting = new MeetingWithLobbyModel($appSession->meetingParticipant->meeting);
         }
 

+ 2 - 0
app/HttpModels/LobbyModel.php

@@ -6,6 +6,7 @@ use App\Models\Lobby;
 class LobbyModel extends ClientLobbyModel {
     public $isStrangerAccessible;
     public $isClientAccessible;
+    public $isActive;
     public $meetings = [];
 
     public function __construct(Lobby $lobby)
@@ -13,6 +14,7 @@ class LobbyModel extends ClientLobbyModel {
         parent::__construct($lobby);
         $this->isStrangerAccessible = $lobby->is_stranger_accessible;
         $this->isClientAccessible = $lobby->is_client_accessible;
+        $this->isActive = $lobby->is_active;
         foreach ($lobby->meetings()->where('meeting.is_active',true)->get() as $meeting) {
             $this->meetings[] = new MeetingModel($meeting);
         }

+ 143 - 48
resources/js/components/pages/ClientEntrance.vue

@@ -24,7 +24,7 @@
                 <v-stepper-content step="1">
                     <v-card class="mb-12" color="grey lighten-1" height="200px">
                         <div class="checkin-form d-flex justify-content-center align-items-center">
-                            <div>
+                            <form ref="baseForm">
                                 <!-- <input type="hidden" name="lobbyUid" value />
                                 <input type="hidden" name="_api" value="/api/meeting/createAsStrangerPerformer" />
                                 <input type="hidden" name="_success" value="/pro/login" />
@@ -42,7 +42,7 @@
                                 <div class="input-group mb-3">
                                     <input type="date" name="strangerDob" class="form-control" placeholder="Date of Birth" v-model="user.dateOfBirth" required />
                                 </div>
-                            </div>
+                            </form>
                         </div>
                     </v-card>
 
@@ -108,6 +108,16 @@ import { mapState } from "vuex";
 
 import VueGridLayout from "vue-grid-layout";
 
+import VueSocketIO from "vue-socket.io";
+import SocketIO from "socket.io-client";
+
+Vue.use(
+    new VueSocketIO({
+        debug: true,
+        connection: SocketIO(process.env.MIX_SOCKET_SERVICE_URL)
+    })
+);
+
 export default {
     components: {
         GridLayout: VueGridLayout.GridLayout,
@@ -120,12 +130,11 @@ export default {
         },
         meetingProp: {
             type: Object
+        },
+        clientUid: {
+            type: String,
+            required: true
         }
-        // ,
-        // lobby_uid: {
-        //     type: String,
-        //     required: true
-        // }
     },
     data() {
         return {
@@ -135,7 +144,8 @@ export default {
                 dateOfBirth: null
             },
             meetingUid: "",
-            stepper: 1,
+            meetingName: sessionStorage.getItem("meeting_name") || "",
+            stepper: sessionStorage.getItem("step") || 1,
             cameraWorkingConfirmed: false,
             loading: false,
             /* Copied */
@@ -159,11 +169,47 @@ export default {
             loadingInProgress: false
         };
     },
+    sockets: {},
+    watch: {
+        publisherReady(val) {
+            if (val && this.sessionConnected) this.publishToSession();
+        },
+        sessionConnected(val) {
+            if (val && this.publisherReady) this.publishToSession();
+        },
+        "user.is_active_and_visible"(val) {
+            if (!this.publisher || !this.publisher.publishVideo) return;
+            this.publisher.publishVideo(val);
+            this.publisher.publishAudio(val);
+        },
+        "meeting.id"(val) {
+            if (this.loadingInProgress) return;
+            this.loadingInProgress = true;
+            this.disconnect();
+
+            if (val) {
+                this.getToken({}, true);
+            } else {
+                this.loadingInProgress = false;
+            }
+        },
+        stepper: {
+            handler(newVal) {
+                sessionStorage.setItem("step", newVal);
+            }
+        }
+    },
     methods: {
         checkIn() {
             this.loading = true;
+            this.meetingName = `${this.user.firstName} ${this.user.lastName} ${this.user.dateOfBirth}`;
+            if (!this.$refs.baseForm.reportValidity()) {
+                this.loading = false;
+                return;
+            }
+
             $.ajax({
-                url: "/post-to-api",
+                url: "/post-to-api-ajax",
                 method: "POST",
                 headers: {
                     "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
@@ -172,15 +218,35 @@ export default {
                 data: {
                     _api: "/api/meeting/createAsStrangerPerformer",
                     lobbyUid: this.lobbyProp.uid,
-                    title: `${this.user.firstName} ${this.user.lastName} ${this.user.dateOfBirth}`,
+                    title: this.meetingName,
                     strangerFirstName: this.user.firstName,
                     strangerLastName: this.user.lastName,
                     strangerDob: this.user.dateOfBirth
                 },
                 success: data => {
+                    if (!data.success) {
+                        alert(data.message);
+                        return;
+                    }
                     this.stepper = 2;
                     this.meetingUid = data.data;
-                    this.$nextTick(this.initializePublisher);
+
+                    this.$socket.emit("userData", {
+                        user: {
+                            uid: this.clientUid,
+                            name: `${this.firstName} ${this.lastName}`,
+                            type: "STRANGER"
+                        },
+                        meeting: {
+                            uid: this.meetingUid,
+                            lobby_uid: this.lobbyProp.uid
+                        },
+                        lobbies_uid_list: [`${this.lobbyProp.uid}`]
+                    });
+
+                    if (!this.publisher) {
+                        this.$nextTick(this.initializePublisher);
+                    }
                 },
                 error: jXhr => {},
                 complete: () => {
@@ -215,6 +281,10 @@ export default {
                             cont.obj = this.publisher;
                             this.$set(cont, "self", true);
                             this.cameraWorkingConfirmed = true;
+
+                            if (this.stepper == 3) {
+                                this.gotoStep3();
+                            }
                         });
                     }
                 }
@@ -229,7 +299,10 @@ export default {
             });
         },
         gotoStep3() {
-            this.stepper = 3;
+            if (this.stepper != 3) {
+                this.stepper = 3;
+            }
+
             this.videoGridPreview = [];
             this.adjustVideoContainers();
             this.$nextTick(() => {
@@ -237,6 +310,17 @@ export default {
                 cont.el = $(`#${cont.id}`)[0];
                 cont.el.appendChild(cont.obj.element);
                 this.getToken();
+
+                this.$socket.emit("meetingJoined", {
+                    lobby_uid: this.lobbyProp.uid,
+                    meeting_name: this.meetingName,
+                    meeting_uid: this.meetingUid,
+                    user: {
+                        name: `${this.firstName} ${this.lastName}`,
+                        type: "STRANGER",
+                        uid: this.clientUid
+                    }
+                });
             });
         },
         /* Copied */
@@ -254,14 +338,13 @@ export default {
             if (this.screenPublisher) {
                 this.screenPublisher.destroy();
                 this.screenPublisher = null;
-                this.$store.commit("setScreenShareState", null);
             }
         },
         getToken() {
             // if (this.meeting.scheduledDate && !this.meeting.startedAt && this.meeting.scheduledDate > new Date() && (this.user.type === "guest" || !confirm("Meeting not started. Start it now?")))
             //     return;
             $.ajax({
-                url: "/post-to-api",
+                url: "/post-to-api-ajax",
                 method: "POST",
                 headers: {
                     "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
@@ -271,6 +354,10 @@ export default {
                     uid: this.meetingUid
                 },
                 success: data => {
+                    if (!data.success) {
+                        alert(data.message);
+                        return;
+                    }
                     // this.$refs.initialModal.hide();
                     // if (!isAssociate) {
                     //     const user = Object.assign({}, this.user);
@@ -401,7 +488,8 @@ export default {
             this.openTokSession.on({
                 sessionDisconnected: event => {
                     if (event.reason === "forceDisconnected") {
-                        alert("You were kicked.");
+                        alert("You were kicked out of meeting.");
+                        //window.location.reload();
                         //TODO: Kicked
                         // if (this.user.type === "associate") {
                         //     this.disconnect();
@@ -494,9 +582,9 @@ export default {
                 this.subscribers.push(subscriber);
             });
 
-            console.log(sessionId);
+            /* console.log(sessionId);
             console.log(token);
-            console.log(apiKey);
+            console.log(apiKey); */
             this.openTokSession.connect(token, error => {
                 // If the connection is successful, publish to the session
                 if (error) {
@@ -518,7 +606,7 @@ export default {
             this.$nextTick(function() {
                 this.readyForUse = true;
                 if (this.user.type === "associate") {
-                    if (!this.meeting.id || this.active_menu_item.template !== "room") return;
+                    if (!this.meeting.id) return;
                     this.getToken({}, true);
                     return;
                 }
@@ -538,36 +626,41 @@ export default {
             });
         }
     },
-    watch: {
-        publisherReady(val) {
-            if (val && this.sessionConnected) this.publishToSession();
-        },
-        sessionConnected(val) {
-            if (val && this.publisherReady) this.publishToSession();
-        },
-        "user.is_active_and_visible"(val) {
-            if (!this.publisher || !this.publisher.publishVideo) return;
-            this.publisher.publishVideo(val);
-            this.publisher.publishAudio(val);
-        },
-        "meeting.id"(val) {
-            if (this.loadingInProgress) return;
-            this.loadingInProgress = true;
-            this.disconnect();
-
-            if (val) {
-                this.getToken({}, true);
-            } else {
-                this.loadingInProgress = false;
+    created() {
+        if (this.meetingProp) {
+            this.user.firstName = this.meetingProp.strangerFirstName;
+            this.user.lastName = this.meetingProp.strangerLastName;
+            this.user.dateOfBirth = this.meetingProp.strangerDob;
+
+            if (this.stepper == 1) {
+                this.stepper = 2;
             }
-        },
-        "active_menu_item.template"(val) {
-            if (this.loadingInProgress || val !== "room" || !this.meeting.id || this.openTokSession) return;
-            this.loadingInProgress = true;
-            this.getToken({}, true);
+
+            this.$socket.emit("userData", {
+                user: {
+                    uid: this.clientUid,
+                    name: `${this.firstName} ${this.lastName}`,
+                    type: "STRANGER"
+                },
+                meeting: {
+                    uid: this.meetingUid,
+                    lobby_uid: this.lobbyProp.uid
+                },
+                lobbies_uid_list: [`${this.lobbyProp.uid}`]
+            });
         }
     },
     mounted() {
+        if (this.meetingProp) {
+            this.meetingUid = this.meetingProp.uid;
+            this.$nextTick(this.initializePublisher);
+            let self = this;
+        }
+
+        this.sockets.subscribe("meeting-closed", data => {
+            this.$eventBus.$emit("leaveMeeting")
+        })
+
         let self = this;
 
         this.$eventBus.$on("meetingRejoin", () => {
@@ -590,17 +683,19 @@ export default {
 
         this.$eventBus.$on("leaveMeeting", () => {
             $.ajax({
-                url: "/associate/meeting/leave",
+                url: "/post-to-api-ajax",
                 method: "POST",
                 headers: {
                     "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
                 },
                 error: jXhr => {
-                    console.error(getSingleError(jXhr));
+                    console.error(getSingleError(jXhr))
                 }
             });
-            this.$store.dispatch("leaveMeeting");
-            this.disconnect();
+            this.$store.dispatch("leaveMeeting")
+            this.$socket.emit('meetingLeft')
+            this.disconnect()
+            alert('Meeting was Closed.')
         });
     }
 };

+ 106 - 17
resources/js/components/pages/MeetingsAppRoot.vue

@@ -9,7 +9,11 @@
             </v-expansion-panel>
 
             <v-expansion-panel class="no-v-padding">
-                <v-expansion-panel-header>Meeting Room</v-expansion-panel-header>
+                <v-expansion-panel-header>
+                    <span>Meeting Room</span>
+                    <v-spacer></v-spacer>
+                    <v-btn color="purple" small ripple outlined class="mr-3" @click.stop="leaveMeeting">Leave</v-btn>
+                </v-expansion-panel-header>
                 <v-expansion-panel-content>
                     <meeting-room></meeting-room>
                 </v-expansion-panel-content>
@@ -59,19 +63,50 @@ export default {
         return {
             active_panel: [0, 1, 2],
             incomingCallDetails: null,
-            lobbies: [
-                {
-                    lobbyUid: "1",
-                    name: "Test Lobby",
-                    description: null,
-                    isStrangerAccessible: true,
-                    isClientAccessible: true
-                }
-            ]
+            lobbies: []
         };
     },
     computed: {
-        ...mapState(["user"])
+        ...mapState(["user", "meeting"])
+    },
+    watch: {
+        meeting: {
+            handler(newVal, oldVal) {
+                if (oldVal && oldVal.uid) {
+                    this.$socket.emit("meetingLeft", { lobby_uid: oldVal.lobby_uid, meeting_name: oldVal.name, meeting_uid: oldVal.uid, user: this.user });
+                }
+
+
+                if (newVal && newVal.uid) {
+                    this.$socket.emit("meetingJoined", { lobby_uid: newVal.lobby_uid, meeting_name: newVal.name, meeting_uid: newVal.uid, user: this.user });
+
+                    /* axios
+                        .get(`/meeting/${this.meeting.id}/messages`)
+                        .then(response => {
+                            this.messages = response.data.map(cur => {
+                                // cur.id = cur.message_id
+                                cur.timestamp_formatted = timeago.format(cur.timestamp);
+
+                                // cur.user = {
+                                //     name: cur.name,
+                                //     UID: cur.user_uid,
+                                //     avatarFile: cur.avatarSrc || null
+                                // }
+                                cur.sent = this.user.UID == cur.user.UID ? true : false;
+
+                                // delete cur.message_id
+                                // delete cur.name
+                                // delete cur.user_uid
+
+                                return cur;
+                            });
+                        })
+                        .catch(e => {
+                            console.log(e);
+                        }); */
+                }
+            }
+        }
     },
     sockets: {
         incomingCall: function(data) {
@@ -93,17 +128,22 @@ export default {
                     }
                 }
             }, 1000);
+        },
+        reconnect: function() {
+            if (this.meeting.uid) {
+                this.$socket.emit("meetingJoined", { lobby_uid: this.meeting.lobby_uid, meeting_uid: this.meeting.uid, user: this.user });
+            }
         }
     },
     methods: {
-        //
+        leaveMeeting(){
+            this.$eventBus.$emit('leaveMeeting')
+        }
     },
     mounted() {
         let lobbies = this.userProp.lobbies;
         delete this.userProp.lobbies;
 
-        this.$socket.emit("userData", { user: this.userProp });
-
         lobbies.map(lobby => {
             lobby.selected_meeting = null;
             lobby.meetings.map(meeting => {
@@ -113,19 +153,68 @@ export default {
         });
 
         if (this.meetingProp) {
-            this.$store.commit("setMeeting", this.meetingProp);
+            let meeting = {
+                uid: this.meetingProp.uid,
+                lobby_uid: this.meetingProp.lobby.uid,
+                name: this.meetingProp.name,
+                active_members: [],
+                pros_online: []
+            }
+            this.$store.commit("setCurrentMeeting", meeting);
         }
 
+        this.$socket.emit("userData", {
+            user: this.userProp,
+            meeting: this.meeting,
+            lobbies_uid_list: lobbies.reduce((acc, cur) => { console.log(cur); return [...acc, cur.uid] }, [])
+        })
+
         this.$store.commit("setInitialUser", this.userProp);
         this.$store.commit("setLobbies", lobbies);
 
         this.$socket.emit("lobbyDataRequest", lobbies);
 
         this.sockets.subscribe("lobby-data", data => {
-            for (let meeting of data.meetings) {
+            for (let meeting of Object.values(data.meetings)) {
                 this.$store.commit("setLobbyActivity", meeting);
             }
-        });
+        })
+
+        /* Meeting Handlers */
+
+        this.sockets.subscribe("meeting-activity", data => {
+            this.$store.commit("setLobbyActivity", data)
+        })
+
+        this.sockets.subscribe("meeting-created", data => {
+            this.$store.commit("addNewMeetingInLobby", data);
+        })
+
+        this.sockets.subscribe("meeting-updated", data => {
+            this.$store.commit("setUpdatedMeetingInLobby", data);
+        })
+
+        this.sockets.subscribe("meeting-closed", data => {
+            this.$eventBus.$emit("leaveMeeting")
+        })
+
+        /* Lobby Handlers */
+
+        this.sockets.subscribe("lobby-updated", data => {
+            this.$store.commit("updateLobby", data)
+        })
+
+        this.sockets.subscribe("lobby-activated", data => {
+            this.$store.commit("activateLobby", data);
+        })
+
+        this.sockets.subscribe("lobby-deactivated", data => {
+            this.$store.commit("deactivateLobby", data);
+        })
+
+        this.sockets.subscribe("lobby-created-for-pro", data => {
+            this.$store.commit("addNewLobby", data);
+        })
     }
 };
 </script>

+ 103 - 70
resources/js/components/partials/LobbyList.vue

@@ -1,81 +1,85 @@
 <template>
     <div>
-        <v-card v-for="lobby in lobbies" flat :key="lobby.id" :loading="loading">
-            <div class="d-flex flex-row justify-content-between">
-                <div>
-                    <v-card-title>{{lobby.name}}</v-card-title>
+        <template v-for="lobby in lobbies">
+            <v-card v-if="lobby.isActive" flat :key="lobby.id" :loading="loading">
+                <div class="d-flex flex-row justify-content-between">
+                    <div>
+                        <v-card-title>{{lobby.name}}</v-card-title>
 
-                    <v-card-text>
-                        <div>{{lobby.description || 'No description available.'}}</div>
-                    </v-card-text>
+                        <v-card-text>
+                            <div>{{lobby.description || 'No description available.'}}</div>
+                        </v-card-text>
+                    </div>
                 </div>
-            </div>
 
-            <v-divider class="mx-4"></v-divider>
+                <v-divider class="mx-4"></v-divider>
 
-            <v-list dense v-if="meetingListFiltered(lobby.meetings).length">
-                <v-container class="overflow-y-auto px-0 pt-0">
+                <v-list dense v-if="meetingListFiltered(lobby.meetings).length">
+                    <v-container class="overflow-y-auto px-0 pt-0">
+                        <v-subheader class="d-flex flex-row justify-content-between">
+                            <span>Meetings ({{meetingListFiltered(lobby.meetings).length}})</span>
+                        </v-subheader>
+                    </v-container>
+                    <v-container style="height: 210px" class="overflow-y-auto">
+                        <v-row>
+                            <v-list-item-group class="w-100" v-model="lobby.selected_meeting" color="primary">
+                                <v-list-item v-for="(item, i) in meetingListFiltered(lobby.meetings)" :key="i" :value="item">
+                                    <v-list-item-icon>
+                                        <v-icon v-text="'mdi-clock'"></v-icon>
+                                    </v-list-item-icon>
+                                    <v-list-item-content>
+                                        <v-list-item-title v-text="item.name"></v-list-item-title>
+                                        <v-tooltip top v-if="item.pros_online.length > 0">
+                                            <template v-slot:activator="{ on }">
+                                                <v-list-item-subtitle v-on="on" v-text="item.pros_online.length > 0 ? `PROs Connected (${item.pros_online.length})` : 'No PROs Connected'"></v-list-item-subtitle>
+                                            </template>
+                                            <span v-for="pro in item.pros_online" :key="pro.UID">
+                                                {{pro.name}}
+                                                <br />
+                                            </span>
+                                        </v-tooltip>
+                                        <v-list-item-subtitle v-else v-text="item.pros_online.length > 0 ? `PROs Connected (${item.pros_online.length})` : 'No PROs Connected'"></v-list-item-subtitle>
+                                    </v-list-item-content>
+                                    <v-list-item-action>
+                                        <v-list-item-action-text :class="{'status':true, 'is-active':item.active_members.length > 0}" v-text="'Online: ' + item.active_members.length"></v-list-item-action-text>
+                                    </v-list-item-action>
+                                </v-list-item>
+                            </v-list-item-group>
+                        </v-row>
+                    </v-container>
+                </v-list>
+
+                <v-list v-else>
                     <v-subheader class="d-flex flex-row justify-content-between">
                         <span>Meetings ({{meetingListFiltered(lobby.meetings).length}})</span>
                     </v-subheader>
-                </v-container>
-                <v-container style="height: 210px" class="overflow-y-auto">
-                    <v-row>
-                        <v-list-item-group class="w-100" v-model="lobby.selected_meeting" color="primary">
-                            <v-list-item v-for="(item, i) in meetingListFiltered(lobby.meetings)" :key="i" :value="item">
-                                <v-list-item-icon>
-                                    <v-icon v-text="'mdi-clock'"></v-icon>
-                                </v-list-item-icon>
-                                <v-list-item-content>
-                                    <v-list-item-title v-text="item.name"></v-list-item-title>
-                                    <v-tooltip top v-if="item.pros_online.length > 0">
-                                        <template v-slot:activator="{ on }">
-                                            <v-list-item-subtitle v-on="on" v-text="item.pros_online.length > 0 ? `PROs Connected (${item.pros_online.length})` : 'No PROs Connected'"></v-list-item-subtitle>
-                                        </template>
-                                        <span v-for="pro in item.pros_online" :key="pro.UID">
-                                            {{pro.name}}
-                                            <br />
-                                        </span>
-                                    </v-tooltip>
-                                    <v-list-item-subtitle v-else v-text="item.pros_online.length > 0 ? `PROs Connected (${item.pros_online.length})` : 'No PROs Connected'"></v-list-item-subtitle>
-                                </v-list-item-content>
-                                <v-list-item-action>
-                                    <v-list-item-action-text :class="{'status':true, 'is-active':item.active_members.length > 0}" v-text="'Online: ' + item.active_members.length"></v-list-item-action-text>
-                                </v-list-item-action>
-                            </v-list-item>
-                        </v-list-item-group>
-                    </v-row>
-                </v-container>
-            </v-list>
-
-            <v-list v-else>
-                <v-subheader class="d-flex flex-row justify-content-between">
-                    <span>Meetings ({{meetingListFiltered(lobby.meetings).length}})</span>
-                </v-subheader>
-                <v-container style="height: 210px" class="overflow-y-auto">
-                    <v-row>
-                        <v-list-item-group class="w-100 text-center" color="primary">
-                            <v-list-item>
-                                <v-list-item-content>
-                                    <v-list-item-title muted v-text="'No Meetings found for this lobby.'"></v-list-item-title>
-                                </v-list-item-content>
-                            </v-list-item>
-                        </v-list-item-group>
-                    </v-row>
-                </v-container>
-            </v-list>
+                    <v-container style="height: 210px" class="overflow-y-auto">
+                        <v-row>
+                            <v-list-item-group class="w-100 text-center" color="primary">
+                                <v-list-item>
+                                    <v-list-item-content>
+                                        <v-list-item-title muted v-text="'No Meetings found for this lobby.'"></v-list-item-title>
+                                    </v-list-item-content>
+                                </v-list-item>
+                            </v-list-item-group>
+                        </v-row>
+                    </v-container>
+                </v-list>
 
-            <transition-expand>
-                <div v-if="meetingListFiltered(lobby.meetings).length">
-                    <v-divider class="mx-4"></v-divider>
+                <transition-expand>
+                    <div v-if="meetingListFiltered(lobby.meetings).length">
+                        <v-divider class="mx-4"></v-divider>
 
-                    <v-card-actions>
-                        <v-btn :disabled="lobby.selected_meeting !== 0 && !lobby.selected_meeting" color="deep-purple accent-4" text @click="joinMeeting(lobby)">Join</v-btn>
-                        <v-btn :disabled="lobby.selected_meeting !== 0 && !lobby.selected_meeting" color="deep-purple accent-4" text @click="inviteToMeeting(lobby)">Invite</v-btn>
-                    </v-card-actions>
-                </div>
-            </transition-expand>
-        </v-card>
+                        <v-card-actions>
+                            <v-btn :disabled="lobby.selected_meeting !== 0 && !lobby.selected_meeting" color="deep-purple accent-4" text @click="joinMeeting(lobby)">Join</v-btn>
+                            <v-btn :disabled="lobby.selected_meeting !== 0 && !lobby.selected_meeting" color="deep-purple accent-4" text @click="inviteToMeeting(lobby)">Invite</v-btn>
+                            <v-spacer></v-spacer>
+                            <v-btn :disabled="lobby.selected_meeting !== 0 && !lobby.selected_meeting" color="deep-purple accent-4" text @click="closeMeeting(lobby)">Close</v-btn>
+                        </v-card-actions>
+                    </div>
+                </transition-expand>
+            </v-card>
+        </template>
     </div>
 </template>
 
@@ -93,12 +97,12 @@ export default {
     },
     methods: {
         meetingListFiltered(meetings) {
-            return meetings.filter(x => x.pros_online.length == 0 || x.pros_online.findIndex(y => y.uid == this.user.uid) !== -1);
+            return meetings.filter(x => x.active_members.length > 0 && (x.pros_online.length == 0 || x.pros_online.findIndex(y => y.uid == this.user.uid) !== -1));
         },
         joinMeeting(lobby) {
             this.loading = true;
             $.ajax({
-                url: "/post-to-api",
+                url: "/post-to-api-ajax",
                 method: "POST",
                 headers: {
                     "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
@@ -109,11 +113,40 @@ export default {
                     uid: lobby.selected_meeting.uid
                 },
                 success: data => {
+                    if (!data.success) {
+                        alert(data.message);
+                        return;
+                    }
                     if (this.meeting.uid === lobby.selected_meeting.uid) {
                         this.$eventBus.$emit("meetingRejoin");
                     } else {
                         lobby.selected_meeting.lobby = lobby;
-                        this.$store.commit("setMeeting", lobby.selected_meeting);
+                        this.$store.commit("setCurrentMeeting", { lobby_uid: lobby.uid, ...lobby.selected_meeting });
+                    }
+                },
+                error: jXhr => {},
+                complete: () => {
+                    this.loading = false;
+                }
+            });
+        },
+        closeMeeting(lobby) {
+            this.loading = true;
+            $.ajax({
+                url: "/post-to-api-ajax",
+                method: "POST",
+                headers: {
+                    "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
+                },
+                dataType: "json",
+                data: {
+                    _api: "/api/meeting/close",
+                    uid: lobby.selected_meeting.uid
+                },
+                success: data => {
+                    if (!data.success) {
+                        alert(data.message);
+                        return;
                     }
                 },
                 error: jXhr => {},

+ 48 - 98
resources/js/components/partials/MeetingRoom.vue

@@ -16,7 +16,7 @@
                     :use-css-transforms="true"
                 >
                     <grid-item v-for="(video, index) in videoGrid" :key="video.id" :id="video.id" :x="video.x" :y="video.y" :w="video.w" :h="video.h" :i="video.i">
-                        <v-btn :color="'#282e38'" fab large dark class="v-btn--kick" @click="kickParcitipant(video)" v-show="!video.video.self && user.type == 'associate'">
+                        <v-btn :color="'#282e38'" fab large dark class="v-btn--kick" @click="kickParcitipant(video)" v-show="!video.video.self && user.type == 'PRO'">
                             <v-icon>mdi-account-remove</v-icon>
                         </v-btn>
                     </grid-item>
@@ -45,6 +45,9 @@ export default {
         GridLayout: VueGridLayout.GridLayout,
         GridItem: VueGridLayout.GridItem
     },
+    computed: {
+        ...mapState(["meeting", "user", "session"])
+    },
     data() {
         return {
             readyForUse: false,
@@ -52,7 +55,6 @@ export default {
             counter: 1,
             openTokSession: null,
             publisher: null,
-            screenPublisher: null,
             subscribers: [],
             publisherReady: false,
             accessDialogShown: false,
@@ -67,9 +69,6 @@ export default {
             loadingInProgress: false
         };
     },
-    computed: {
-        ...mapState(["meeting", "user", "session"])
-    },
     methods: {
         disconnect() {
             if (!this.openTokSession) return;
@@ -82,15 +81,10 @@ export default {
             this.publisherReady = false;
             this.$store.commit("setSessionConnectivityState", false);
             this.openTokSession = null;
-            if (this.screenPublisher) {
-                this.screenPublisher.destroy();
-                this.screenPublisher = null;
-                this.$store.commit("setScreenShareState", null);
-            }
         },
         getToken() {
             $.ajax({
-                url: "/post-to-api",
+                url: "/post-to-api-ajax",
                 method: "POST",
                 headers: {
                     "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
@@ -100,6 +94,10 @@ export default {
                     uid: this.meeting.uid
                 },
                 success: data => {
+                    if (!data.success) {
+                        alert(data.message);
+                        return;
+                    }
                     this.initializeOpenTok(data.data.apiKey, data.data.sessionId, data.data.token);
                 },
                 error: jXhr => {
@@ -223,13 +221,8 @@ export default {
             this.openTokSession.on({
                 sessionDisconnected: event => {
                     if (event.reason === "forceDisconnected") {
-                        alert("You were kicked.");
-                        if (this.user.type === "associate") {
-                            this.disconnect();
-                            this.$store.dispatch("leaveMeeting");
-                        } else {
-                            window.location = "/";
-                        }
+                        alert("You were kicked out of meeting.");
+                        this.$store.dispatch("leaveMeeting");
                     }
                 }
             });
@@ -345,7 +338,7 @@ export default {
             this.$nextTick(function() {
                 this.readyForUse = true;
                 if (this.user.type === "associate") {
-                    if (!this.meeting.id || this.active_menu_item.template !== "room") return;
+                    if (!this.meeting.id) return;
                     this.getToken({}, true);
                     return;
                 }
@@ -364,72 +357,29 @@ export default {
                 });
             });
         },
-        shareScreen() {
-            if (this.screenPublisher) return;
-            this.screenPublisher = OT.initPublisher(
-                this.$refs.otContainer,
-                {
-                    insertMode: "append",
-                    width: "100%",
-                    height: "100%",
-                    resolution: "1280x720",
-                    frameRate: 30,
-                    videoSource: "screen",
-                    name: this.user.name,
-                    style: {
-                        nameDisplayMode: "on",
-                        archiveStatusDisplayMode: "off"
-                    }
-                },
-                error => {
-                    if (error) {
-                        console.error(error);
-                        if (error.code !== 1500) alert(error.message);
-                        this.screenPublisher = null;
-                    } else {
-                        const cont = this.createVideoContainer();
-                        this.$nextTick(() => {
-                            cont.el = $(`#${cont.id}`)[0];
-                            cont.el.appendChild(this.screenPublisher.element);
-                            cont.obj = this.screenPublisher;
-                            // cont.self = true;
-
-                            this.$set(cont, "self", true);
-                        });
-                        this.screenPublisher.on("streamDestroyed", event => {
-                            cont.el.remove();
-                            const index = this.videos.findIndex(v => v.id == cont.id);
-                            if (index >= 0) this.videos.splice(index, 1);
-                            this.adjustVideoContainers();
-                            this.screenPublisher = null;
-                            this.$store.commit("setScreenShareState", false);
-                        });
-                        this.openTokSession.publish(this.screenPublisher, error => {
-                            if (error) {
-                                alert(error.message);
-                            }
-                        });
-                        this.$store.commit("setScreenShareState", true);
-                    }
-                }
-            );
-        },
         kickParcitipantContinue(participant) {
             const connection = participant.video.obj.stream.connection;
-            const [type, id] = connection.data.split(":");
+            const id = connection.data;
             this.openTokSession.forceDisconnect(connection, error => {
                 if (error) {
                     console.error(error);
                 } else {
                     $.ajax({
-                        url: `/associate/meeting/${this.meeting.id}/kick`,
+                        url: "/post-to-api-ajax",
                         method: "POST",
                         headers: {
                             "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
                         },
                         data: {
-                            type,
-                            id
+                            _api: "/api/meeting/kick",
+                            uid: this.meeting.uid,
+                            participantUid: id
+                        },
+                        success: data => {
+                            if (!data.success) {
+                                alert(data.message);
+                                return;
+                            }
                         },
                         error: jXhr => {
                             console.error(getSingleError(jXhr));
@@ -440,6 +390,29 @@ export default {
         },
         kickParcitipant(participant) {
             if (confirm("Are you sure want to kick this person out of meeting?")) this.kickParcitipantContinue(participant);
+        },
+        leaveMeeting() {
+            $.ajax({
+                url: "/post-to-api-ajax",
+                method: "POST",
+                headers: {
+                    "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
+                },
+                data: {
+                    _api: "/api/meeting/leave"
+                },
+                success: data => {
+                    if (!data.success) {
+                        alert(data.message);
+                        return;
+                    }
+                    this.$store.dispatch("leaveMeeting");
+                },
+                error: jXhr => {
+                    console.error(getSingleError(jXhr));
+                }
+            });
+            this.$store.dispatch("leaveMeeting");
         }
     },
     watch: {
@@ -457,25 +430,15 @@ export default {
         "meeting.uid"(val) {
             if (this.loadingInProgress) return;
             this.loadingInProgress = true;
-            this.disconnect();
 
             if (val) {
                 this.getToken();
             } else {
+                this.disconnect()
                 this.loadingInProgress = false;
             }
-        },
-        "active_menu_item.template"(val) {
-            if (this.loadingInProgress || val !== "room" || !this.meeting.id || this.openTokSession) return;
-            this.loadingInProgress = true;
-            this.getToken({}, true);
         }
     },
-    created() {
-        OT.checkScreenSharingCapability(response => {
-            this.$store.commit("setScreesharingAvailability", response.supported);
-        });
-    },
     mounted() {
         let self = this;
 
@@ -498,20 +461,7 @@ export default {
             }
         });
 
-        this.$eventBus.$on("leaveMeeting", () => {
-            // $.ajax({
-            //     url: "/associate/meeting/leave",
-            //     method: "POST",
-            //     headers: {
-            //         "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
-            //     },
-            //     error: jXhr => {
-            //         console.error(getSingleError(jXhr));
-            //     }
-            // });
-            this.$store.dispatch("leaveMeeting");
-            this.disconnect();
-        });
+        this.$eventBus.$on("leaveMeeting", this.leaveMeeting);
 
         if (this.meeting.uid) {
             this.getToken();

+ 67 - 51
resources/js/components/vuex/index.js

@@ -20,19 +20,12 @@ export default () => new Vuex.Store({
             sessionConnected: false,
             screenSharingActive: false
         },
-        active_menu_item: {
-            name: 'Lobbies',
-            class: 'mdi mdi-view-dashboard',
-            template: 'lobby-list'
-        },
         meeting: {
-            id: '',
+            uid: null,
+            lobby_uid: null,
             name: '',
-            passwordRequired: false,
-            lobby: null,
-            active_members: 0,
-            scheduledDate: null,
-            startedAt: null
+            active_members: [],
+            pros_online: []
         },
         lobbies: [],
         settings: {
@@ -40,76 +33,101 @@ export default () => new Vuex.Store({
         }
     },
     mutations: {
-        setUsername: (state, data) => {
-            state.user.name = data
-        },
-        setUsertype: (state, data) => {
-            state.user.type = data
-        },
+
+        /* User */
+
         setInitialUser(state, data) {
             state.user = data
         },
-        setUser(state, data) {
-            state.user = data
-        },
         setUserAvatarFile(state, data) {
             state.user.avatarFile = data
         },
         setUserActivityState: (state, data) => {
             state.user.is_active_and_visible = data
         },
-        setScreenShareState: (state, data) => {
-            state.session.screenSharingActive = data
-        },
+
+        /* Lobbies */
+
         setLobbies(state, data) {
             state.lobbies = data
         },
         addNewLobby(state, data) {
             state.lobbies.push(data)
         },
-        setSingleLobby(state, data) {
-            let lobby = state.lobbies.findIndex((cur) => cur.id == data.id)
+        updateLobby(state, data) {
+            let lobby = state.lobbies.filter((cur) => cur.uid == data.uid)
+
+            if (lobby.length) {
+                for(let prop in data){
+                    lobby[0][prop] = data[prop]
+                }
+            }
+        },
+        activateLobby(state, data) {
+            let lobby = state.lobbies.filter((cur) => cur.uid == data.uid)
+
+            if(lobby.length){
+                lobby.isActive = true
+            }
+        },
+        deactivateLobby(state, data) {
+            let lobby = state.lobbies.filter((cur) => cur.uid == data.uid)
 
-            if (lobby !== -1) {
-                state.lobbies[lobby].meetings = data.meetings.map(m => {
-                    m.active_members = []
-                    m.pros_online = []
-                    return m
-                });
+            if(lobby.length){
+                lobby.isActive = false
             }
         },
+
         setLobbyActivity(state, data) {
-            let lobby = state.lobbies.filter((cur) => cur.id == data.lobbyID)
+            let lobby = state.lobbies.filter((cur) => cur.uid == data.lobby_uid)
             if (lobby.length) {
-                let meeting = lobby[0].meetings.filter((cur) => cur.id == data.meetingID)
+                let meeting = lobby[0].meetings.filter((cur) => cur.uid == data.meeting_uid)
+
                 if (meeting.length) {
-                    meeting[0].active_members = data.active_members
-                    meeting[0].pros_online = data.pros_online
+                    meeting[0].active_members = Object.values(data.active_members)
+                    meeting[0].pros_online = Object.values(data.pros_online)
 
-                    if (state.meeting.id && state.meeting.id == meeting[0].id) {
+                    if (state.meeting.uid && state.meeting.uid == meeting[0].uid) {
                         state.meeting.active_members = data.active_members
                     }
                 }
             }
-
         },
-        setNewMeetingInLobby(state, data) {
-            let lobby = state.lobbies.filter((cur) => cur.id == parseInt(data.lobby.id))
+
+        /* Meetings */
+
+        addNewMeetingInLobby(state, data) {
+            let lobby = state.lobbies.filter((cur) => cur.uid == data.lobby.uid)
 
             if (lobby.length) {
-                data.finalDate = new Date(data.scheduled_date ? data.scheduled_date : data.created_at)
+                data.active_members = []
+                data.pros_online = []
+
                 lobby[0].meetings.push(data)
                 lobby[0].meetings.sort((a, b) => {
                     return a.finalDate - b.finalDate
                 })
             }
         },
-        setMeeting(state, data) {
-            state.meeting = data
+        setUpdatedMeetingInLobby(state, data) {
+            let lobby = state.lobbies.filter((cur) => cur.uid == data.lobby.uid)
+
+            if (lobby.length) {
+                let meeting = lobby[0].meetings.filter((cur) => cur.uid == data.uid)
+
+                if(meeting.length){
+                    for(let prop in data){
+                        meeting[0][prop] = data[prop]
+                    }
+                }
+            }
         },
-        setScreesharingAvailability(state, data) {
-            state.session.screenSharingAvailable = data
+        setCurrentMeeting(state, data) {
+            state.meeting = data
         },
+
+        /* Other */
+
         setSessionConnectivityState(state, data) {
             state.session.sessionConnected = data
         },
@@ -120,16 +138,14 @@ export default () => new Vuex.Store({
     actions: {
         leaveMeeting(store) {
             let meeting = {
-                id: '',
+                uid: null,
+                lobby_uid: null,
                 name: '',
-                passwordRequired: false,
-                lobby: null,
-                active_members: 0,
-                scheduledDate: null,
-                startedAt: null
+                active_members: [],
+                pros_online: []
             }
 
-            store.commit('setMeeting', meeting)
+            store.commit('setCurrentMeeting', meeting)
         }
     }
 })

+ 21 - 0
resources/sass/app.scss

@@ -52,6 +52,27 @@ $orange: #F08322;
     transform: scale(0.3);
 }
 
+/* Grid */
+
+.vue-grid-item {
+	.v-btn.v-btn--kick {
+		bottom: 0;
+		left: 50%;
+		transform: translateX(-50%);
+		position: fixed;
+		margin-bottom: 8px;
+		z-index: 10000 !important;
+		transition: all 0.3s;
+		opacity: 0;
+	}
+
+	&:hover {
+		.v-btn.v-btn--kick {
+			opacity: 1
+		}
+	}
+}
+
 /* MC */
 
 .mc-page {

+ 1 - 0
resources/views/client/index.blade.php

@@ -4,6 +4,7 @@
     <client-entrance 
         :lobby-prop="{!! str_replace('"','\'',str_replace('\'','\\\'',json_encode($lobbyModel))) !!}"
         :meeting-prop="{!! str_replace('"','\'',str_replace('\'','\\\'',json_encode($meeting))) !!}"
+        client-uid="{{ $sessionKey }}"
     ></client-entrance>
 </v-app>
 @endsection

+ 1 - 0
routes/web.php

@@ -58,6 +58,7 @@ Route::middleware('ensureValidSession')->group(function(){
 });
 
 Route::post('/post-to-api', 'AppSessionController@postToAPI')->name('post-to-api');
+Route::post('/post-to-api-ajax', 'AppSessionController@postToAPIAjax')->name('post-to-api-ajax');
 
 Route::get('/client/{url_slug}', 'ClientController@entrance')->name('client-entrance');