浏览代码

added client custom pae

Josh Kamau 5 年之前
父节点
当前提交
cf466066cc

+ 25 - 3
app/Http/Controllers/ClientController.php

@@ -7,6 +7,7 @@ use Illuminate\Support\Facades\Http;
 use App\HttpModels\ClientLobbyModel;
 use App\HttpModels\MeetingModel;
 use App\Models\AppSession;
+use App\Models\MeetingParticipant;
 
 use Cookie;
 
@@ -26,6 +27,9 @@ class ClientController extends Controller
         $sessionKey = Cookie::get('sessionKey');
 
         $lobbyModel = new ClientLobbyModel($lobby);
+        session([
+            'lobbyId' => $lobby->id
+        ]);
         // $response = response()->view('client/index',compact('lobbyModel'),200);
 
         if(!$sessionKey){
@@ -50,13 +54,31 @@ class ClientController extends Controller
         // else {
         //     return view('client/index');
         // }
+        return view('client/index',compact('lobbyModel','sessionKey'));
+    }
+    
+    public function meeting(MeetingParticipant $meetingParticipant)
+    {
+        $sessionKey = Cookie::get('sessionKey');
+        if (!$meetingParticipant || !$meetingParticipant->id || !session('lobbyId')) {
+            \abort(404);
+            return;
+        }
+
+        $lobby = Lobby::find(session('lobbyId'));
+        $lobbyModel = new ClientLobbyModel($lobby);
         $session = AppSession::where("session_key",$sessionKey)->first();
         $meeting = null;
-        if ($session->meetingParticipant 
+        if ($session->meetingParticipant
+                // && $session->meetingParticipant->is_active 
                 && $session->meetingParticipant->meeting->is_active
                 && ($session->meetingParticipant->meeting->lobby_id === $lobby->id || $session->meetingParticipant->meeting->lobby_id == null) ) {
             $meeting = new MeetingModel($session->meetingParticipant->meeting);
         }
-        return view('client/index',compact('lobbyModel','meeting','sessionKey'));
-	}
+        else {
+            \abort(404);
+            return; 
+        }
+        return view('client/meeting',compact('lobbyModel','meeting','sessionKey'));
+    }
 }

+ 3 - 1
app/Http/Controllers/clients_SINGLE_Controller.php

@@ -1,3 +1,4 @@
+<?php /* DO NOT GENERATE */ ?>
 <?php
 
 namespace App\Http\Controllers;
@@ -5,6 +6,7 @@ namespace App\Http\Controllers;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Auth;
+use App\Models\Client;
 
 class clients_SINGLE_Controller extends Controller
 {
@@ -385,7 +387,7 @@ class clients_SINGLE_Controller extends Controller
 
 	// GET /clients/view/{uid}/SUB_dashboard
 	public function SUB_dashboard(Request $request, $uid) {
-		$record = DB::table('client')->where('uid', $uid)->first();
+		$record = Client::where("uid", $uid)->first();
 		return response()->view('pro/clients_SINGLE/SUB_dashboard', compact('record'), session('message') ? 500 : 200)->header('Content-Type', 'text/html');
 	}
 

+ 4 - 1
app/HttpModels/MeetingWithLobbyModel.php

@@ -12,7 +12,10 @@ class MeetingWithLobbyModel extends MeetingModel {
     {
         parent::__construct($meeting);
 
-        $this->lobby = new ClientLobbyModel($meeting->lobby);
+        if ($meeting->lobby_id)
+        {
+            $this->lobby = new ClientLobbyModel($meeting->lobby);
+        }
         if ($meeting->target_lobby_pro_id)
             $this->targetPro = new LobbyProModel($meeting->targetLobbyPro->pro);
     }

+ 12 - 0
app/Models/Client.php

@@ -13,4 +13,16 @@ class Client extends Model
     {
         return $this->hasMany(Note::class, 'client_id', 'id');
     }
+
+    public function duplicateOfClient(){
+        return $this->hasOne(Client::class, 'id', 'duplicate_of_client_id');
+    }
+
+    public function mcpPro(){
+        return $this->hasOne(Pro::class, 'id', 'mcp_pro_id');
+    }
+
+    public function name_display(){
+        return $this->name_first . ' '.$this->name_last;
+    }
 }

+ 22 - 557
resources/js/components/pages/ClientEntrance.vue

@@ -50,66 +50,14 @@
 
                     <v-btn color="primary" @click="checkIn" :loading="loading">Continue</v-btn>
                 </v-stepper-content>
-
-                <v-stepper-content step="2">
-                    <p>
-                        Please make sure your webcam is working. Accept the access request to your hardware on the top left to continue.
-                        <br />Then, someone should be with you shortly.
-                    </p>
-
-                    <v-card class="mb-12" color="grey lighten-1" height="50vh">
-                        <grid-layout
-                            :layout="videoGridPreview"
-                            :key="`preview_${uniqueId}`"
-                            :col-num="maxCols"
-                            :max-rows="maxRows"
-                            :row-height="rowHeight"
-                            :is-draggable="false"
-                            :is-resizable="false"
-                            :verticalCompact="true"
-                            :margin="[gridPadding, gridPadding]"
-                            :use-css-transforms="true"
-                        >
-                            <grid-item v-for="video in videoGridPreview" :key="video.id" :id="video.id" :x="video.x" :y="video.y" :w="video.w" :h="video.h" :i="video.i"></grid-item>
-                        </grid-layout>
-                    </v-card>
-
-                    <v-btn color="primary" @click="gotoStep3" :disabled="!cameraWorkingConfirmed">Continue</v-btn>
-
-                    <!-- <v-btn text @click="stepper = 1">Cancel</v-btn> -->
-                </v-stepper-content>
-
-                <v-stepper-content step="3">
-                    <p>Great! Please wait a little, doctor should join shortly.</p>
-
-                    <v-card class="mb-12" color="grey lighten-1" height="50vh">
-                        <grid-layout
-                            :layout="videoGrid"
-                            :key="uniqueId"
-                            :col-num="maxCols"
-                            :max-rows="maxRows"
-                            :row-height="rowHeight"
-                            :is-draggable="false"
-                            :is-resizable="false"
-                            :verticalCompact="true"
-                            :margin="[gridPadding, gridPadding]"
-                            :use-css-transforms="true"
-                        >
-                            <grid-item v-for="video in videoGrid" :key="video.id" :id="video.id" :x="video.x" :y="video.y" :w="video.w" :h="video.h" :i="video.i"></grid-item>
-                        </grid-layout>
-                    </v-card>
-                </v-stepper-content>
             </v-stepper-items>
         </v-stepper>
-        <div style="display: none" ref="otContainer"></div>
     </div>
 </template>
 
 <script>
 import { mapState } from "vuex";
 
-import VueGridLayout from "vue-grid-layout";
-
 import VueSocketIO from "vue-socket.io";
 import SocketIO from "socket.io-client";
 
@@ -121,10 +69,6 @@ Vue.use(
 );
 
 export default {
-    components: {
-        GridLayout: VueGridLayout.GridLayout,
-        GridItem: VueGridLayout.GridItem
-    },
     props: {
         lobbyProp: {
             type: Object,
@@ -134,9 +78,6 @@ export default {
                 pros: []
             }
         },
-        meetingProp: {
-            type: Object
-        },
         clientUid: {
             type: String,
             required: true
@@ -160,8 +101,8 @@ export default {
                 targetLobbyProUid: ""
             },
             meetingUid: "",
-            meetingName: sessionStorage.getItem("meeting_name") || "",
-            stepper: sessionStorage.getItem("step") || 1,
+            meetingName: this.meetingProp.name,
+            stepper: 1,
             cameraWorkingConfirmed: false,
             loading: false,
             /* Copied */
@@ -216,28 +157,6 @@ 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;
-            }
-        },
         stepper: {
             handler(newVal) {
                 sessionStorage.setItem("step", newVal);
@@ -274,490 +193,36 @@ export default {
                         alert(data.message);
                         return;
                     }
-                    this.stepper = 2;
-                    this.meetingUid = data.data;
-
-                    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);
-                    }
+                    window.location = `/client/meeting-participant/${data.data}`;
+                    // this.stepper = 2;
+                    // this.meetingUid = data.data;
+
+                    // 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: () => {
                     this.loading = false;
                 }
             });
-        },
-        initializePublisher() {
-            this.publisher = OT.initPublisher(
-                this.$refs.otContainer,
-                {
-                    insertMode: "append",
-                    width: "100%",
-                    height: "100%",
-                    resolution: "1280x720",
-                    frameRate: 30,
-                    name: `${this.user.firstName} ${this.user.lastName}`,
-                    style: {
-                        nameDisplayMode: "on",
-                        archiveStatusDisplayMode: "off"
-                    }
-                },
-                error => {
-                    if (error) {
-                        alert(error.message);
-                    } else {
-                        this.publisherReady = true;
-                        const cont = this.createVideoContainer(true);
-                        this.$nextTick(() => {
-                            cont.el = $(`#${cont.id}`)[0];
-                            cont.el.appendChild(this.publisher.element);
-                            cont.obj = this.publisher;
-                            this.$set(cont, "self", true);
-                            this.cameraWorkingConfirmed = true;
-
-                            if (this.stepper == 3) {
-                                this.gotoStep3();
-                            }
-                        });
-                    }
-                }
-            );
-            this.publisher.on({
-                accessDialogOpened: e => {
-                    this.accessDialogShown = true;
-                },
-                accessDialogClosed: e => {
-                    this.accessDialogShown = false;
-                }
-            });
-        },
-        gotoStep3() {
-            if (this.stepper != 3) {
-                this.stepper = 3;
-            }
-
-            this.videoGridPreview = [];
-            this.adjustVideoContainers();
-            this.$nextTick(() => {
-                const cont = this.videos[0];
-                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
-                    }
-                });
-                $.ajax({
-                    url: "/post-to-api-ajax",
-                    method: "POST",
-                    headers: {
-                        "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
-                    },
-                    data: {
-                        _api: "/api/meeting/startLobbyCall",
-                        uid: this.meetingUid,
-                        lobbyUid: this.lobbyProp.uid
-                    },
-                    success: data => {
-                        if (!data.success) console.log(data.message);
-                    }
-                });
-            });
-        },
-        /* Copied */
-        disconnect() {
-            if (!this.openTokSession) return;
-            this.openTokSession.disconnect();
-            this.openTokSession.off();
-            this.videos = [];
-            this.publisher.destroy();
-            this.publisher = null;
-            this.subscribers = [];
-            this.publisherReady = false;
-            this.$store.commit("setSessionConnectivityState", false);
-            this.openTokSession = null;
-            if (this.screenPublisher) {
-                this.screenPublisher.destroy();
-                this.screenPublisher = 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-ajax",
-                method: "POST",
-                headers: {
-                    "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
-                },
-                data: {
-                    _api: "/api/meeting/getToken",
-                    uid: this.meetingUid
-                },
-                success: data => {
-                    if (!data.success) {
-                        alert(data.message);
-                        return;
-                    }
-                    // this.$refs.initialModal.hide();
-                    // if (!isAssociate) {
-                    //     const user = Object.assign({}, this.user);
-                    //     user.id = data.participantId;
-                    //     user.name = data.participantName;
-                    //     user.pin = data.participantPin;
-                    //     this.$store.commit("setUser", user);
-                    // }
-                    this.initializeOpenTok(data.data.apiKey, data.data.sessionId, data.data.token);
-                },
-                error: jXhr => {
-                    if (jXhr.responseJSON && jXhr.responseJSON.errorCode) {
-                        switch (jXhr.responseJSON.errorCode) {
-                            case "LM-1":
-                                alert("Meeting not started yet.");
-                                break;
-                            case "LM-2":
-                                alert("You was kicked from this meeting.");
-                                window.location = "/";
-                                break;
-                            case "LM-3":
-                                alert("Wrong password");
-                                this.$refs.initialModal.disableWaiting();
-                                break;
-                        }
-                    } else {
-                        // this.$refs.initialModal.disableWaiting();
-                        alert(jXhr.responseJSON.message);
-                    }
-                }
-            });
-        },
-        createVideoContainer(preview = false) {
-            let videoCont = {};
-            videoCont.id = `${this.uniqueId}_video_${this.counter++}`;
-
-            this.videos.push(videoCont);
-            this.adjustVideoContainers(preview);
-
-            return videoCont;
-        },
-        adjustVideoContainers(preview = false) {
-            let videoGrid;
-            if (preview) {
-                videoGrid = this.videoGridPreview = [];
-            } else {
-                videoGrid = this.videoGrid = [];
-            }
-
-            let windowHeight = window.innerHeight / 2;
-            this.rowHeight = (windowHeight - this.gridPadding * (this.maxRows + 1)) / this.maxRows;
-
-            let cols = Math.ceil(Math.sqrt(this.videos.length));
-            let rows = Math.ceil(this.videos.length / cols);
-
-            let elementsLastRow = this.videos.length % cols;
-            let lastNormalIndex = this.videos.length - elementsLastRow;
-
-            if (elementsLastRow) {
-                this.maxCols = cols * elementsLastRow;
-            } else {
-                this.maxCols = cols;
-            }
-
-            let colsPerElement = this.maxCols / cols;
-            let rowsPerElement = this.maxRows / rows;
-
-            let colsLastRow = Math.ceil(this.maxCols / elementsLastRow);
-
-            let cntX = 0;
-            let cntY = 0;
-
-            for (let [index, video] of this.videos.entries()) {
-                video.i = index;
-                video.x = cntX;
-                video.y = cntY;
-                video.h = rowsPerElement;
-                video.w = colsPerElement;
-                cntX += colsPerElement;
-
-                if (cntX >= this.maxCols) {
-                    cntX = 0;
-                    cntY += rowsPerElement;
-                }
-
-                let videoTemp = Object.assign({}, video);
-                videoTemp.video = video;
-                videoGrid.push(videoTemp);
-            }
-
-            /* OLD IMPLEMENTATION */
-
-            /* for (let [index, video] of this.videos.entries()) {
-                video.i = index
-                if (index < lastNormalIndex) {
-                    video.x = cntX
-                    video.y = cntY
-                    video.h = rowsPerElement
-                    video.w = colsPerElement
-                    cntX += colsPerElement
-
-                    if (cntX >= this.maxCols) {
-                        cntX = 0
-                        cntY += rowsPerElement
-                    }
-                } else {
-                    video.x = cntX
-                    video.y = cntY
-                    video.h = rowsPerElement
-                    video.w = colsLastRow
-                    cntX += colsLastRow
-                }
-
-                let videoTemp = Object.assign({}, video)
-                this.videoGrid.push(videoTemp)
-            } */
-
-            /* let gridColumn, gridRows = ''
-
-            if(rows && cols){
-                gridColumn = `grid-template-columns: repeat(${cols}, minmax(320px, 1fr))`
-                gridRows = `grid-template-rows: repeat(${rows}, minmax(240px, 1fr))`
-            } */
-        },
-        initializeOpenTok(apiKey, sessionId, token) {
-            this.openTokSession = OT.initSession(apiKey, sessionId);
-
-            this.openTokSession.on({
-                sessionDisconnected: event => {
-                    if (event.reason === "forceDisconnected") {
-                        alert("You were kicked out of meeting.");
-                        //window.location.reload();
-                        //TODO: Kicked
-                        // if (this.user.type === "associate") {
-                        //     this.disconnect();
-                        //     this.$store.dispatch("leaveMeeting");
-                        // } else {
-                        //     window.location = "/";
-                        // }
-                    }
-                }
-            });
-
-            // Create a publisher
-            // this.publisher = OT.initPublisher(
-            //     this.$refs.otContainer,
-            //     {
-            //         insertMode: "append",
-            //         width: "100%",
-            //         height: "100%",
-            //         resolution: "1280x720",
-            //         frameRate: 30,
-            //         name: this.user.name,
-            //         style: {
-            //             nameDisplayMode: "on",
-            //             archiveStatusDisplayMode: "off"
-            //         }
-            //     },
-            //     error => {
-            //         if (error) {
-            //             alert(error.message);
-            //         } else {
-            //             this.publisherReady = true;
-            //             const cont = this.createVideoContainer();
-            //             this.$nextTick(() => {
-            //                 cont.el = $(`#${cont.id}`)[0];
-            //                 cont.el.appendChild(this.publisher.element);
-            //                 cont.obj = this.publisher;
-            //                 this.$set(cont, "self", true);
-            //                 //cont.self = true
-            //             });
-            //         }
-            //     }
-            // );
-
-            // this.publisher.on({
-            //     accessDialogOpened: e => {
-            //         this.accessDialogShown = true;
-            //     },
-            //     accessDialogClosed: e => {
-            //         this.accessDialogShown = false;
-            //     }
-            // });
-
-            this.openTokSession.on("streamCreated", event => {
-                //console.log("stream Created event")
-                let container;
-                if (event.stream.videoType === "screen") {
-                    //TODO: This is screenshare
-                }
-                const subscriber = this.openTokSession.subscribe(
-                    event.stream,
-                    this.$refs.otContainer,
-                    {
-                        insertMode: "append",
-                        width: "100%",
-                        height: "100%",
-                        style: { nameDisplayMode: "on" }
-                    },
-                    error => {
-                        if (error) {
-                            alert(error.message);
-                        } else {
-                            const cont = this.createVideoContainer();
-                            this.$nextTick(() => {
-                                cont.el = $(`#${cont.id}`)[0];
-                                cont.el.appendChild(subscriber.element);
-                                cont.obj = subscriber;
-                                container = cont;
-                            });
-                        }
-                    }
-                );
-                subscriber.on({
-                    destroyed: e => {
-                        container.el.remove();
-                        const index = this.videos.findIndex(v => v.id == container.id);
-                        if (index >= 0) this.videos.splice(index, 1);
-                        this.adjustVideoContainers();
-                    }
-                });
-                this.subscribers.push(subscriber);
-            });
-
-            /* console.log(sessionId);
-            console.log(token);
-            console.log(apiKey); */
-            this.openTokSession.connect(token, error => {
-                // If the connection is successful, publish to the session
-                if (error) {
-                    alert(error.message);
-                } else {
-                    this.sessionConnected = true;
-                }
-            });
-            this.loadingInProgress = false;
-        },
-        publishToSession() {
-            this.openTokSession.publish(this.publisher, error => {
-                if (error) {
-                    alert(error.message);
-                }
-            });
-        },
-        ready() {
-            this.$nextTick(function() {
-                this.readyForUse = true;
-                if (this.user.type === "associate") {
-                    if (!this.meeting.id) return;
-                    this.getToken({}, true);
-                    return;
-                }
-
-                if (this.meeting.scheduledDate && !this.meeting.startedAt && this.meeting.scheduledDate > new Date()) {
-                    alert("Meeting not started.");
-                    return;
-                }
-                let participantId = this.user.id;
-                if (!participantId) {
-                    this.$refs.initialModal.show(this.meeting.passwordRequired);
-                    return;
-                }
-                this.getToken({
-                    participantId
-                });
-            });
-        }
-    },
-    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;
-            }
-
-            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", () => {
-            this.loadingInProgress = true;
-            this.disconnect();
-            this.getToken({}, true);
-        });
-
-        let width = $(window).width();
-        let height = $(window).height();
-
-        window.addEventListener("resize", function() {
-            let new_width = $(window).width();
-            let new_height = $(window).height();
-
-            if (width !== new_width || height !== new_height) {
-                self.adjustVideoContainers();
-            }
-        });
-
-        this.$eventBus.$on("leaveMeeting", () => {
-            $.ajax({
-                url: "/post-to-api-ajax",
-                method: "POST",
-                headers: {
-                    "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
-                },
-                error: jXhr => {
-                    console.error(getSingleError(jXhr));
-                }
-            });
-            this.$store.dispatch("leaveMeeting");
-            this.$socket.emit("meetingLeft");
-            this.disconnect();
-            alert("Meeting was Closed.");
-        });
         this.prosList.push(...this.lobbyProp.pros);
     }
 };

+ 696 - 0
resources/js/components/pages/ClientMeeting.vue

@@ -0,0 +1,696 @@
+<template>
+    <div class="login-box">
+        <div class="login-logo auth-branding text-center border-0">
+            <span class="brand-text font-weight-light text-white">
+                Welcome to
+                <!-- <b>Dr. Smith & Dr. Brown's Office</b>! -->
+                <b>{{ lobbyProp.name }}</b>
+            </span>
+        </div>
+        <v-stepper v-model="stepper">
+            <v-stepper-header>
+                <v-stepper-step :complete="stepper > 1" step="1">Check In</v-stepper-step>
+
+                <v-divider></v-divider>
+
+                <v-stepper-step :complete="stepper > 2" step="2">Test your webcam</v-stepper-step>
+
+                <v-divider></v-divider>
+
+                <v-stepper-step step="3">Meet your Doctor</v-stepper-step>
+            </v-stepper-header>
+
+            <v-stepper-items>
+                <v-stepper-content step="2">
+                    <p>
+                        Please make sure your webcam is working. Accept the access request to your hardware on the top left to continue.
+                        <br />Then, someone should be with you shortly.
+                    </p>
+
+                    <v-card class="mb-12" color="grey lighten-1" height="50vh">
+                        <grid-layout
+                            :layout="videoGridPreview"
+                            :key="`preview_${uniqueId}`"
+                            :col-num="maxCols"
+                            :max-rows="maxRows"
+                            :row-height="rowHeight"
+                            :is-draggable="false"
+                            :is-resizable="false"
+                            :verticalCompact="true"
+                            :margin="[gridPadding, gridPadding]"
+                            :use-css-transforms="true"
+                        >
+                            <grid-item v-for="video in videoGridPreview" :key="video.id" :id="video.id" :x="video.x" :y="video.y" :w="video.w" :h="video.h" :i="video.i"></grid-item>
+                        </grid-layout>
+                    </v-card>
+
+                    <v-btn color="primary" @click="gotoStep3" :disabled="!cameraWorkingConfirmed">Continue</v-btn>
+
+                    <!-- <v-btn text @click="stepper = 1">Cancel</v-btn> -->
+                </v-stepper-content>
+
+                <v-stepper-content step="3">
+                    <p>Great! Please wait a little, doctor should join shortly.</p>
+
+                    <v-card class="mb-12" color="grey lighten-1" height="50vh">
+                        <grid-layout
+                            :layout="videoGrid"
+                            :key="uniqueId"
+                            :col-num="maxCols"
+                            :max-rows="maxRows"
+                            :row-height="rowHeight"
+                            :is-draggable="false"
+                            :is-resizable="false"
+                            :verticalCompact="true"
+                            :margin="[gridPadding, gridPadding]"
+                            :use-css-transforms="true"
+                        >
+                            <grid-item v-for="video in videoGrid" :key="video.id" :id="video.id" :x="video.x" :y="video.y" :w="video.w" :h="video.h" :i="video.i"></grid-item>
+                        </grid-layout>
+                    </v-card>
+                </v-stepper-content>
+            </v-stepper-items>
+        </v-stepper>
+        <div style="display: none" ref="otContainer"></div>
+    </div>
+</template>
+
+<script>
+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,
+        GridItem: VueGridLayout.GridItem
+    },
+    props: {
+        lobbyProp: {
+            type: Object,
+            default: {
+                uid: null,
+                name: "Base",
+                pros: []
+            }
+        },
+        meetingProp: {
+            type: Object
+        },
+        clientUid: {
+            type: String,
+            required: true
+        }
+    },
+    computed: {
+        // prosList(){
+        //     return [{
+        //         name: 'Test',
+        //         type: 'Cardiologist',
+        //         uid: 'someuid'
+        //     }]
+        // }
+    },
+    data() {
+        return {
+            user: {
+                firstName: "",
+                lastName: "",
+                dateOfBirth: null,
+                targetLobbyProUid: ""
+            },
+            meetingUid: "",
+            meetingName: sessionStorage.getItem("meeting_name") || "",
+            stepper: 2,
+            cameraWorkingConfirmed: false,
+            loading: false,
+            /* Copied */
+            readyForUse: false,
+            uniqueId: Math.floor(Math.random() * Math.floor(10000)),
+            counter: 1,
+            openTokSession: null,
+            publisher: null,
+            screenPublisher: null,
+            subscribers: [],
+            publisherReady: false,
+            sessionConnected: false,
+            accessDialogShown: false,
+            videos: [],
+            videoGrid: [],
+            videoGridPreview: [],
+            maxCols: 12,
+            maxRows: 4,
+            rowHeight: 240,
+            gridPadding: 8,
+            loadingInProgress: false,
+            prosList: []
+        };
+    },
+    sockets: {
+        reconnect: function() {
+            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.meetingUid) {
+                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
+                    }
+                });
+            }
+        }
+    },
+    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(`${this.meetingProp.uid}.step`, newVal);
+            }
+        }
+    },
+    methods: {
+        initializePublisher() {
+            this.publisher = OT.initPublisher(
+                this.$refs.otContainer,
+                {
+                    insertMode: "append",
+                    width: "100%",
+                    height: "100%",
+                    resolution: "1280x720",
+                    frameRate: 30,
+                    name: `${this.user.firstName} ${this.user.lastName}`,
+                    style: {
+                        nameDisplayMode: "on",
+                        archiveStatusDisplayMode: "off"
+                    }
+                },
+                error => {
+                    if (error) {
+                        alert(error.message);
+                    } else {
+                        this.publisherReady = true;
+                        const cont = this.createVideoContainer(true);
+                        this.$nextTick(() => {
+                            cont.el = $(`#${cont.id}`)[0];
+                            cont.el.appendChild(this.publisher.element);
+                            cont.obj = this.publisher;
+                            this.$set(cont, "self", true);
+                            this.cameraWorkingConfirmed = true;
+
+                            if (this.stepper == 3) {
+                                this.gotoStep3();
+                            }
+                        });
+                    }
+                }
+            );
+            this.publisher.on({
+                accessDialogOpened: e => {
+                    this.accessDialogShown = true;
+                },
+                accessDialogClosed: e => {
+                    this.accessDialogShown = false;
+                }
+            });
+        },
+        gotoStep3() {
+            if (this.stepper != 3) {
+                this.stepper = 3;
+            }
+
+            this.videoGridPreview = [];
+            this.adjustVideoContainers();
+            this.$nextTick(() => {
+                const cont = this.videos[0];
+                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
+                    }
+                });
+                $.ajax({
+                    url: "/post-to-api-ajax",
+                    method: "POST",
+                    headers: {
+                        "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
+                    },
+                    data: {
+                        _api: "/api/meeting/startLobbyCall",
+                        uid: this.meetingUid,
+                        lobbyUid: this.lobbyProp.uid
+                    },
+                    success: data => {
+                        if (!data.success) console.log(data.message);
+                    }
+                });
+            });
+        },
+        /* Copied */
+        disconnect() {
+            if (!this.openTokSession) return;
+            this.openTokSession.disconnect();
+            this.openTokSession.off();
+            this.videos = [];
+            this.publisher.destroy();
+            this.publisher = null;
+            this.subscribers = [];
+            this.publisherReady = false;
+            this.$store.commit("setSessionConnectivityState", false);
+            this.openTokSession = null;
+            if (this.screenPublisher) {
+                this.screenPublisher.destroy();
+                this.screenPublisher = 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-ajax",
+                method: "POST",
+                headers: {
+                    "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
+                },
+                data: {
+                    _api: "/api/meeting/getToken",
+                    uid: this.meetingUid
+                },
+                success: data => {
+                    if (!data.success) {
+                        alert(data.message);
+                        return;
+                    }
+                    // this.$refs.initialModal.hide();
+                    // if (!isAssociate) {
+                    //     const user = Object.assign({}, this.user);
+                    //     user.id = data.participantId;
+                    //     user.name = data.participantName;
+                    //     user.pin = data.participantPin;
+                    //     this.$store.commit("setUser", user);
+                    // }
+                    this.initializeOpenTok(data.data.apiKey, data.data.sessionId, data.data.token);
+                },
+                error: jXhr => {
+                    if (jXhr.responseJSON && jXhr.responseJSON.errorCode) {
+                        switch (jXhr.responseJSON.errorCode) {
+                            case "LM-1":
+                                alert("Meeting not started yet.");
+                                break;
+                            case "LM-2":
+                                alert("You was kicked from this meeting.");
+                                window.location = "/";
+                                break;
+                            case "LM-3":
+                                alert("Wrong password");
+                                this.$refs.initialModal.disableWaiting();
+                                break;
+                        }
+                    } else {
+                        // this.$refs.initialModal.disableWaiting();
+                        alert(jXhr.responseJSON.message);
+                    }
+                }
+            });
+        },
+        createVideoContainer(preview = false) {
+            let videoCont = {};
+            videoCont.id = `${this.uniqueId}_video_${this.counter++}`;
+
+            this.videos.push(videoCont);
+            this.adjustVideoContainers(preview);
+
+            return videoCont;
+        },
+        adjustVideoContainers(preview = false) {
+            let videoGrid;
+            if (preview) {
+                videoGrid = this.videoGridPreview = [];
+            } else {
+                videoGrid = this.videoGrid = [];
+            }
+
+            let windowHeight = window.innerHeight / 2;
+            this.rowHeight = (windowHeight - this.gridPadding * (this.maxRows + 1)) / this.maxRows;
+
+            let cols = Math.ceil(Math.sqrt(this.videos.length));
+            let rows = Math.ceil(this.videos.length / cols);
+
+            let elementsLastRow = this.videos.length % cols;
+            let lastNormalIndex = this.videos.length - elementsLastRow;
+
+            if (elementsLastRow) {
+                this.maxCols = cols * elementsLastRow;
+            } else {
+                this.maxCols = cols;
+            }
+
+            let colsPerElement = this.maxCols / cols;
+            let rowsPerElement = this.maxRows / rows;
+
+            let colsLastRow = Math.ceil(this.maxCols / elementsLastRow);
+
+            let cntX = 0;
+            let cntY = 0;
+
+            for (let [index, video] of this.videos.entries()) {
+                video.i = index;
+                video.x = cntX;
+                video.y = cntY;
+                video.h = rowsPerElement;
+                video.w = colsPerElement;
+                cntX += colsPerElement;
+
+                if (cntX >= this.maxCols) {
+                    cntX = 0;
+                    cntY += rowsPerElement;
+                }
+
+                let videoTemp = Object.assign({}, video);
+                videoTemp.video = video;
+                videoGrid.push(videoTemp);
+            }
+
+            /* OLD IMPLEMENTATION */
+
+            /* for (let [index, video] of this.videos.entries()) {
+                video.i = index
+                if (index < lastNormalIndex) {
+                    video.x = cntX
+                    video.y = cntY
+                    video.h = rowsPerElement
+                    video.w = colsPerElement
+                    cntX += colsPerElement
+
+                    if (cntX >= this.maxCols) {
+                        cntX = 0
+                        cntY += rowsPerElement
+                    }
+                } else {
+                    video.x = cntX
+                    video.y = cntY
+                    video.h = rowsPerElement
+                    video.w = colsLastRow
+                    cntX += colsLastRow
+                }
+
+                let videoTemp = Object.assign({}, video)
+                this.videoGrid.push(videoTemp)
+            } */
+
+            /* let gridColumn, gridRows = ''
+
+            if(rows && cols){
+                gridColumn = `grid-template-columns: repeat(${cols}, minmax(320px, 1fr))`
+                gridRows = `grid-template-rows: repeat(${rows}, minmax(240px, 1fr))`
+            } */
+        },
+        initializeOpenTok(apiKey, sessionId, token) {
+            this.openTokSession = OT.initSession(apiKey, sessionId);
+
+            this.openTokSession.on({
+                sessionDisconnected: event => {
+                    if (event.reason === "forceDisconnected") {
+                        alert("You were kicked out of meeting.");
+                        //window.location.reload();
+                        //TODO: Kicked
+                        // if (this.user.type === "associate") {
+                        //     this.disconnect();
+                        //     this.$store.dispatch("leaveMeeting");
+                        // } else {
+                        //     window.location = "/";
+                        // }
+                    }
+                }
+            });
+
+            // Create a publisher
+            // this.publisher = OT.initPublisher(
+            //     this.$refs.otContainer,
+            //     {
+            //         insertMode: "append",
+            //         width: "100%",
+            //         height: "100%",
+            //         resolution: "1280x720",
+            //         frameRate: 30,
+            //         name: this.user.name,
+            //         style: {
+            //             nameDisplayMode: "on",
+            //             archiveStatusDisplayMode: "off"
+            //         }
+            //     },
+            //     error => {
+            //         if (error) {
+            //             alert(error.message);
+            //         } else {
+            //             this.publisherReady = true;
+            //             const cont = this.createVideoContainer();
+            //             this.$nextTick(() => {
+            //                 cont.el = $(`#${cont.id}`)[0];
+            //                 cont.el.appendChild(this.publisher.element);
+            //                 cont.obj = this.publisher;
+            //                 this.$set(cont, "self", true);
+            //                 //cont.self = true
+            //             });
+            //         }
+            //     }
+            // );
+
+            // this.publisher.on({
+            //     accessDialogOpened: e => {
+            //         this.accessDialogShown = true;
+            //     },
+            //     accessDialogClosed: e => {
+            //         this.accessDialogShown = false;
+            //     }
+            // });
+
+            this.openTokSession.on("streamCreated", event => {
+                //console.log("stream Created event")
+                let container;
+                if (event.stream.videoType === "screen") {
+                    //TODO: This is screenshare
+                }
+                const subscriber = this.openTokSession.subscribe(
+                    event.stream,
+                    this.$refs.otContainer,
+                    {
+                        insertMode: "append",
+                        width: "100%",
+                        height: "100%",
+                        style: { nameDisplayMode: "on" }
+                    },
+                    error => {
+                        if (error) {
+                            alert(error.message);
+                        } else {
+                            const cont = this.createVideoContainer();
+                            this.$nextTick(() => {
+                                cont.el = $(`#${cont.id}`)[0];
+                                cont.el.appendChild(subscriber.element);
+                                cont.obj = subscriber;
+                                container = cont;
+                            });
+                        }
+                    }
+                );
+                subscriber.on({
+                    destroyed: e => {
+                        container.el.remove();
+                        const index = this.videos.findIndex(v => v.id == container.id);
+                        if (index >= 0) this.videos.splice(index, 1);
+                        this.adjustVideoContainers();
+                    }
+                });
+                this.subscribers.push(subscriber);
+            });
+
+            /* console.log(sessionId);
+            console.log(token);
+            console.log(apiKey); */
+            this.openTokSession.connect(token, error => {
+                // If the connection is successful, publish to the session
+                if (error) {
+                    alert(error.message);
+                } else {
+                    this.sessionConnected = true;
+                }
+            });
+            this.loadingInProgress = false;
+        },
+        publishToSession() {
+            this.openTokSession.publish(this.publisher, error => {
+                if (error) {
+                    alert(error.message);
+                }
+            });
+        },
+        ready() {
+            this.$nextTick(function() {
+                this.readyForUse = true;
+                if (this.user.type === "associate") {
+                    if (!this.meeting.id) return;
+                    this.getToken({}, true);
+                    return;
+                }
+
+                if (this.meeting.scheduledDate && !this.meeting.startedAt && this.meeting.scheduledDate > new Date()) {
+                    alert("Meeting not started.");
+                    return;
+                }
+                let participantId = this.user.id;
+                if (!participantId) {
+                    this.$refs.initialModal.show(this.meeting.passwordRequired);
+                    return;
+                }
+                this.getToken({
+                    participantId
+                });
+            });
+        }
+    },
+    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;
+            }
+
+            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}`]
+            });
+            this.stepper = sessionStorage.getItem(`${this.meetingProp.uid}.step`) || 2;
+        } else {
+            window.location = "/";
+        }
+    },
+    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", () => {
+            this.loadingInProgress = true;
+            this.disconnect();
+            this.getToken({}, true);
+        });
+
+        let width = $(window).width();
+        let height = $(window).height();
+
+        window.addEventListener("resize", function() {
+            let new_width = $(window).width();
+            let new_height = $(window).height();
+
+            if (width !== new_width || height !== new_height) {
+                self.adjustVideoContainers();
+            }
+        });
+
+        this.$eventBus.$on("leaveMeeting", () => {
+            $.ajax({
+                url: "/post-to-api-ajax",
+                method: "POST",
+                headers: {
+                    "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
+                },
+                error: jXhr => {
+                    console.error(getSingleError(jXhr));
+                }
+            });
+            this.$store.dispatch("leaveMeeting");
+            this.$socket.emit("meetingLeft");
+            this.disconnect();
+            alert("Meeting was Closed.");
+        });
+        this.prosList.push(...this.lobbyProp.pros);
+    }
+};
+</script>
+
+<style lang="scss" scoped>
+.login-box {
+    width: 100%;
+}
+
+.checkin-form {
+    height: 200px;
+
+    form {
+        width: 75%;
+    }
+}
+</style>

+ 101 - 91
resources/js/components/pages/MeetingsAppRoot.vue

@@ -1,53 +1,55 @@
 <template>
-    <v-app>
-        <v-expansion-panels v-model="active_panel" tile hover multiple>
-            <v-expansion-panel class="no-v-padding">
-                <v-expansion-panel-header>Lobby List</v-expansion-panel-header>
-                <v-expansion-panel-content>
-                    <lobby-list></lobby-list>
-                </v-expansion-panel-content>
-            </v-expansion-panel>
-
-            <v-expansion-panel class="no-v-padding">
-                <v-expansion-panel-header>
-                    <span>Meeting Room</span>
-                    <v-spacer></v-spacer>
-                    <v-btn :disabled="meetingLoading" v-if="meeting.uid" 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>
-            </v-expansion-panel>
-
-            <v-expansion-panel>
-                <v-expansion-panel-header>Messenger</v-expansion-panel-header>
-                <v-expansion-panel-content>
-                    <messenger></messenger>
-                </v-expansion-panel-content>
-            </v-expansion-panel>
-
-            <v-expansion-panel>
-                <v-expansion-panel-header>Settings</v-expansion-panel-header>
-                <v-expansion-panel-content>
-                    <settings></settings>
-                </v-expansion-panel-content>
-            </v-expansion-panel>
-        </v-expansion-panels>
+    <div>
+        <v-app>
+            <v-expansion-panels v-model="active_panel" tile hover multiple>
+                <v-expansion-panel class="no-v-padding">
+                    <v-expansion-panel-header>Lobby List</v-expansion-panel-header>
+                    <v-expansion-panel-content>
+                        <lobby-list></lobby-list>
+                    </v-expansion-panel-content>
+                </v-expansion-panel>
+
+                <v-expansion-panel class="no-v-padding">
+                    <v-expansion-panel-header>
+                        <span>Meeting Room</span>
+                        <v-spacer></v-spacer>
+                        <v-btn :disabled="meetingLoading" v-if="meeting.uid" 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>
+                </v-expansion-panel>
+
+                <v-expansion-panel>
+                    <v-expansion-panel-header>Messenger</v-expansion-panel-header>
+                    <v-expansion-panel-content>
+                        <messenger></messenger>
+                    </v-expansion-panel-content>
+                </v-expansion-panel>
+
+                <v-expansion-panel>
+                    <v-expansion-panel-header>Settings</v-expansion-panel-header>
+                    <v-expansion-panel-content>
+                        <settings></settings>
+                    </v-expansion-panel-content>
+                </v-expansion-panel>
+            </v-expansion-panels>
+        </v-app>
         <call-bubble></call-bubble>
-    </v-app>
+    </div>
 </template>
 
 <script>
-import { mapState } from "vuex"
-import VueSocketIO from "vue-socket.io"
-import SocketIO from "socket.io-client"
+import { mapState } from "vuex";
+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 {
     props: {
@@ -63,7 +65,7 @@ export default {
         return {
             active_panel: [0, 1, 2],
             lobbies_: []
-        }
+        };
     },
     computed: {
         ...mapState(["user", "meeting", "lobbies", "meetingLoading"])
@@ -72,13 +74,13 @@ export default {
         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 })
+                    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 })
+                    this.$socket.emit("meetingJoined", { lobby_uid: newVal.lobby_uid, meeting_name: newVal.name, meeting_uid: newVal.uid, user: this.user });
 
-                    this.$eventBus.$emit("updateMeetingMessages")
+                    this.$eventBus.$emit("updateMeetingMessages");
                     /* axios
                         .get(`/meeting/${this.meeting.id}/messages`)
                         .then(response => {
@@ -108,110 +110,118 @@ export default {
         }
     },
     sockets: {
-        incomingCall: function(data){
-            let self = this
+        incomingCall: function(data) {
+            let self = this;
 
-            this.$store.commit('setCall', data)
+            this.$store.commit("setCall", data);
+        },
+        stopRing() {
+            this.$store.commit("rejectCall");
         },
-        disconnect: function(){
-            this.$store.commit("nullifyMeetingsOnline")
+        disconnect: function() {
+            this.$store.commit("nullifyMeetingsOnline");
         },
-        reconnect: function(){
+        reconnect: function() {
             this.$socket.emit("userData", {
                 user: this.user,
                 meeting: this.meeting,
-                lobbies_uid_list: this.lobbies.reduce((acc, cur) => { return [...acc, cur.uid] }, [])
-            })
+                lobbies_uid_list: this.lobbies.reduce((acc, cur) => {
+                    return [...acc, cur.uid];
+                }, [])
+            });
 
             if (this.meeting.uid) {
-                this.$socket.emit("meetingJoined", { lobby_uid: this.meeting.lobby_uid, meeting_uid: this.meeting.uid, user: this.user })
+                this.$socket.emit("meetingJoined", { lobby_uid: this.meeting.lobby_uid, meeting_uid: this.meeting.uid, user: this.user });
             }
         },
-        'call-data': function(data){
-            if(data.callData && data.state == null){
-                this.$store.commit("setCall", data.callData)
+        "call-data": function(data) {
+            if (data.callData && data.state == null) {
+                this.$store.commit("setCall", data.callData);
             }
 
-            this.$socket.emit("lobbyDataRequest")
+            this.$socket.emit("lobbyDataRequest");
         },
-        'lobby-data': function(data){
+        "lobby-data": function(data) {
             for (let meeting of data.meetings) {
-                this.$store.commit("setLobbyActivity", meeting)
+                this.$store.commit("setLobbyActivity", meeting);
             }
         },
 
         /* Meeting Handlers */
 
-        'meeting-activity': function(data){
-            this.$store.commit("setLobbyActivity", data)
+        "meeting-activity": function(data) {
+            console.log(data);
+            this.$store.commit("setLobbyActivity", data);
         },
-        'meeting-created': function(data){
-            this.$store.commit("addNewMeetingInLobby", data)
+        "meeting-created": function(data) {
+            this.$store.commit("addNewMeetingInLobby", data);
         },
-        'meeting-updated': function(data){
-            this.$store.commit("setUpdatedMeetingInLobby", data)
+        "meeting-updated": function(data) {
+            this.$store.commit("setUpdatedMeetingInLobby", data);
         },
-        'meeting-closed': function(data){
+        "meeting-closed": function(data) {
             this.$store.dispatch("leaveMeeting");
         },
 
         /* Lobby Handlers */
 
-        'lobby-updated': function(data){
-            this.$store.commit("updateLobby", data)
+        "lobby-updated": function(data) {
+            this.$store.commit("updateLobby", data);
         },
-        'lobby-activated': function(data){
-            this.$store.commit("activateLobby", data)
+        "lobby-activated": function(data) {
+            this.$store.commit("activateLobby", data);
         },
-        'lobby-deactivated': function(data){
-            this.$store.commit("deactivateLobby", data)
+        "lobby-deactivated": function(data) {
+            this.$store.commit("deactivateLobby", data);
         },
-        'lobby-created-for-pro': function(data){
-            this.$store.commit("addNewLobby", data)
+        "lobby-created-for-pro": function(data) {
+            this.$store.commit("addNewLobby", data);
         }
     },
     methods: {
-        leaveMeeting(){
-            this.$eventBus.$emit('leaveMeeting')
+        leaveMeeting() {
+            this.$eventBus.$emit("leaveMeeting");
         }
     },
     mounted() {
-        let lobbies = this.userProp.lobbies
-        delete this.userProp.lobbies
+        let lobbies = this.userProp.lobbies;
+        delete this.userProp.lobbies;
 
         lobbies.map(lobby => {
-            lobby.selected_meeting = null
+            lobby.selected_meeting = null;
             lobby.meetings.map(meeting => {
-                meeting.active_members = []
-                meeting.pros_online = []
-            })
-        })
+                meeting.active_members = [];
+                meeting.pros_online = [];
+            });
+        });
 
         if (this.meetingProp) {
             let meeting = {
                 uid: this.meetingProp.uid,
-                lobby_uid: this.meetingProp.lobby.uid,
+                lobby_uid: this.meetingProp.lobby ? this.meetingProp.lobby.uid : null,
                 name: this.meetingProp.name,
                 active_members: [],
                 pros_online: []
-            }
-            this.$store.commit("setCurrentMeeting", meeting)
+            };
+            this.$store.commit("setCurrentMeeting", meeting);
         }
 
         this.$socket.emit("userData", {
             user: this.userProp,
             meeting: this.meeting,
-            lobbies_uid_list: lobbies.reduce((acc, cur) => { return [...acc, cur.uid] }, [])
-        })
+            lobbies_uid_list: lobbies.reduce((acc, cur) => {
+                return [...acc, cur.uid];
+            }, [])
+        });
 
-        this.$store.commit("setInitialUser", this.userProp)
-        this.$store.commit("setLobbies", lobbies)
+        this.$store.commit("setInitialUser", this.userProp);
+        this.$store.commit("setLobbies", lobbies);
     }
-}
+};
 </script>
 
 <style>
 .v-expansion-panel-content__wrap {
-    padding: 24px 16px
+    padding: 24px 16px;
 }
 </style>

+ 17 - 17
resources/js/components/partials/LobbyList.vue

@@ -97,20 +97,20 @@ export default {
     },
     methods: {
         meetingListFiltered(lobby) {
-            let visibilityLevel = lobby.lobbyProAccessLevel
-            let meetings = lobby.meetings
+            let visibilityLevel = lobby.lobbyProAccessLevel;
+            let meetings = lobby.meetings;
 
-            if(!visibilityLevel){
-                return []
+            if (!visibilityLevel) {
+                return [];
             }
 
-            switch(visibilityLevel){
-                case 'FREE_FOR_ALL':
-                    return meetings.filter(x => x.active_members.length > 0)
-                case 'VISIBLE_IF_UNATTENDED':
-                    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))
-                case 'SYSTEM_CONTROLLED_BY_LOBBY_CALL':
-                    return []
+            switch (visibilityLevel) {
+                case "FREE_FOR_ALL":
+                    return meetings.filter(x => x.active_members.length > 0);
+                case "VISIBLE_IF_UNATTENDED":
+                    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));
+                case "SYSTEM_CONTROLLED_BY_LOBBY_CALL":
+                    return [];
             }
         },
         joinMeeting(lobby) {
@@ -132,7 +132,7 @@ export default {
                         return;
                     }
                     if (this.meeting.uid === lobby.selected_meeting.uid) {
-                        this.$eventBus.$emit("meetingRejoin")
+                        this.$eventBus.$emit("meetingRejoin");
                     } else {
                         lobby.selected_meeting.lobby = lobby.name;
                         this.$store.commit("setCurrentMeeting", { lobby_uid: lobby.uid, lobby_name: lobby.name, ...lobby.selected_meeting });
@@ -170,12 +170,12 @@ export default {
             });
         }
     },
-    mounted(){
-        let self = this
+    mounted() {
+        let self = this;
 
-        this.$eventBus.$on('joinMeeting', function(data){
-            self.joinMeeting(data)
-        })
+        this.$eventBus.$on("joinMeeting", function(data) {
+            self.joinMeeting(data);
+        });
     }
 };
 </script>

+ 5 - 5
resources/js/components/partials/MeetingRoom.vue

@@ -316,7 +316,7 @@ export default {
                     this.$store.commit("setSessionConnectivityState", true);
                 }
             });
-            this.$store.commit('setMeetingLoadingState', false)
+            this.$store.commit("setMeetingLoadingState", false);
         },
         publishToSession() {
             this.openTokSession.publish(this.publisher, error => {
@@ -429,12 +429,12 @@ export default {
         },
         "meeting.uid"(val) {
             if (this.meetingLoading) return;
-            this.$store.commit('setMeetingLoadingState', true)
+            this.$store.commit("setMeetingLoadingState", true);
             if (val) {
                 this.getToken();
             } else {
-                this.disconnect()
-                this.$store.commit('setMeetingLoadingState', false)
+                this.disconnect();
+                this.$store.commit("setMeetingLoadingState", false);
             }
         }
     },
@@ -443,7 +443,7 @@ export default {
 
         this.$eventBus.$on("screenShare", this.shareScreen);
         this.$eventBus.$on("meetingRejoin", () => {
-            this.$store.commit('setMeetingLoadingState', true)
+            this.$store.commit("setMeetingLoadingState", true);
             this.disconnect();
             this.getToken({}, true);
         });

+ 65 - 23
resources/js/components/vuex/index.js

@@ -35,10 +35,13 @@ export default () => new Vuex.Store({
             }
         },
         meetingLoading: false,
-        callWidget: {
-            active: false,
-            callInfo: {}
-        }
+        callToNoAnswer: null,
+        callWidget: []
+        // {
+        //     active: false,
+        //     noAnswer: false,
+        //     callInfo: {}
+        // }
     },
     mutations: {
 
@@ -66,7 +69,7 @@ export default () => new Vuex.Store({
             let lobby = state.lobbies.filter((cur) => cur.uid == data.uid)
 
             if (lobby.length) {
-                for(let prop in data){
+                for (let prop in data) {
                     lobby[0][prop] = data[prop]
                 }
             }
@@ -74,19 +77,20 @@ export default () => new Vuex.Store({
         activateLobby(state, data) {
             let lobby = state.lobbies.filter((cur) => cur.uid == data.uid)
 
-            if(lobby.length){
+            if (lobby.length) {
                 lobby.isActive = true
             }
         },
         deactivateLobby(state, data) {
             let lobby = state.lobbies.filter((cur) => cur.uid == data.uid)
 
-            if(lobby.length){
+            if (lobby.length) {
                 lobby.isActive = false
             }
         },
 
         setLobbyActivity(state, data) {
+            if (!data || !data.lobby_uid) return
             let lobby = state.lobbies.filter((cur) => cur.uid == data.lobby_uid)
             if (lobby.length) {
                 let meeting = lobby[0].meetings.filter((cur) => cur.uid == data.meeting_uid)
@@ -99,11 +103,12 @@ export default () => new Vuex.Store({
                         state.meeting.active_members = data.active_members
                     }
 
-                    if(state.callWidget.callInfo.meeting_uid == meeting[0].uid){
-                        if(!meeting[0].active_members.length || meeting[0].pros_online.findIndex((x) => x.uid == state.user.uid) !== -1){
-                            state.callWidget.active = false
+                    call = state.callWidget.find(c => c.callInfo.meeting_uid == meeting[0].uid)
+                    if (call) {
+                        if (!meeting[0].active_members.length || meeting[0].pros_online.findIndex((x) => x.uid == state.user.uid) !== -1) {
+                            call.active = false
                         } else {
-                            state.callWidget.active = true
+                            call.active = true
                         }
                     }
                 }
@@ -114,6 +119,7 @@ export default () => new Vuex.Store({
 
         addNewMeetingInLobby(state, data) {
             console.log(data)
+            if (!data.lobby) return
             let lobby = state.lobbies.filter((cur) => cur.uid == data.lobby.uid)
 
             if (lobby.length) {
@@ -132,12 +138,15 @@ export default () => new Vuex.Store({
             if (lobby.length) {
                 let meeting = lobby[0].meetings.filter((cur) => cur.uid == data.uid)
 
-                if(meeting.length){
-                    for(let prop in data){
+                if (meeting.length) {
+                    for (let prop in data) {
                         meeting[0][prop] = data[prop]
                     }
                 }
             }
+            if (state.meeting.uid === data.uid) {
+                state.meeting.name = data.name
+            }
         },
         setCurrentMeeting(state, data) {
             state.meeting = data
@@ -152,31 +161,64 @@ export default () => new Vuex.Store({
 
             state.callWidget.active = false
         },
-        setMeetingLoadingState(state, data){
+        setMeetingLoadingState(state, data) {
             state.meetingLoading = data
         },
 
         /* Other */
 
         setCall(state, data) {
-            state.callWidget.callInfo = data
+            const call = {
+                active: false,
+                timer: null,
+                callInfo: null
+            }
+            call.callInfo = data
 
-            if(state.callWidget.callInfo.time_limit){
-                let timer = setInterval(() => {
-                    if (state.callWidget.callInfo.time_limit > 0) {
-                        state.callWidget.callInfo.time_limit--
+            if (call.callInfo.time_limit) {
+                call.timer = setInterval(() => {
+                    if (call.callInfo.time_limit > 0) {
+                        call.callInfo.time_limit--
 
-                        if (state.callWidget.callInfo.time_limit == 0) {
-                            clearInterval(timer)
+                        if (call.callInfo.time_limit == 0) {
+                            clearInterval(call.timer)
+                            call.timer = null
                             console.log('TIME IS OUT!')
+                            const callIndex = state.callWidget.findIndex(c => c.uid === call.uid)
+                            if (callIndex > -1)
+                                state.callWidget.splice(callIndex, 1)
+                            state.callToNoAnswer = call
+                            $.ajax({
+                                url: "/post-to-api-ajax",
+                                method: "POST",
+                                headers: {
+                                    "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
+                                },
+                                data: {
+                                    _api: "/api/meetingRing/ringNoAnswer",
+                                    uid: call.callInfo.ring_uid
+                                },
+                                error: (jXhr) => console.log(jXhr)
+                            })
                         }
                     }
                 }, 1000)
             }
+
+            if (data.lobby_call_uid)
+                call.active = true
+            state.callWidget.push(call)
         },
-        rejectCall(state) {
+        rejectCall(state, call) {
             state.callWidget.active = false
-            state.callWidget.callInfo = {}
+            if (call.timer)
+                clearInterval(call.timer)
+            const callIndex = state.callWidget.findIndex(c => c.uid === call.uid)
+            if (callIndex > -1)
+                state.callWidget.splice(callIndex, 1)
+            // state.callWidget.callInfo = {}
+            // if (call.timer)
+            //     clearInterval(timer)
         },
         setSessionConnectivityState(state, data) {
             state.session.sessionConnected = data

+ 213 - 142
resources/js/components/widgets/CallBubble.vue

@@ -1,199 +1,270 @@
 <template>
-  <v-btn
-        color="pink"
-        class="callBtn"
-        :class="{'showingAll': showCalleeDetails, 'active': (callWidget.active && callWidget.callInfo && (callWidget.callInfo.time_limit > 0 || callWidget.callInfo.time_limit == null))}"
-        dark
-        absolute
-        bottom
-        right
-        ripple
-        @click="shiftForm"
+    <div class="ring-container">
+        <v-btn
+            v-for="call of callWidget"
+            :key="call.uid"
+            color="pink"
+            class="callBtn"
+            :class="{'showingAll': showCalleeDetailsUid == call.callInfo.uid, 'active': (call.active && call.callInfo && (call.callInfo.time_limit > 0 || call.callInfo.time_limit == null))}"
+            dark
+            ripple
+            @click="shiftForm(call.callInfo)"
         >
-        <div v-if="callWidget.active">
-            <div class="d-flex flex-row align-items-center btnHeader">
-                <v-icon>mdi-video</v-icon>
-                <div class="ml-2 incomingCallMsg">Incoming Call {{!showCalleeDetails && callWidget.callInfo.time_limit !== null ? `(${callWidget.callInfo.time_limit})` : ''}}</div>
-            </div>
-            <div class="fullDetails mt-3">
-                <div class="d-flex flex-row justify-content-between">
-                    <span>Callee:</span>
-                    <span>{{callWidget.callInfo.user_type}}</span>
+            <div v-if="call.active">
+                <div class="d-flex flex-row align-items-center btnHeader">
+                    <v-icon>mdi-video</v-icon>
+                    <div class="ml-2 incomingCallMsg">Incoming Call {{showCalleeDetailsUid != call.callInfo.uid && call.callInfo.time_limit !== null ? `(${call.callInfo.time_limit})` : ''}}</div>
                 </div>
+                <div class="fullDetails mt-3">
+                    <div class="d-flex flex-row justify-content-between">
+                        <span>Callee:</span>
+                        <span>{{call.callInfo.user_type}}</span>
+                    </div>
 
-                <div class="d-flex flex-row justify-content-between">
-                    <span>Lobby:</span>
-                    <span>{{callWidget.callInfo.lobby || 'None'}}</span>
-                </div>
+                    <div class="d-flex flex-row justify-content-between">
+                        <span>Lobby:</span>
+                        <span>{{call.callInfo.lobby || 'None'}}</span>
+                    </div>
 
-                <div class="d-flex flex-row justify-content-center mt-2">
-                    <span>{{callWidget.callInfo.name || 'Unknown'}}</span>
-                </div>
+                    <div class="d-flex flex-row justify-content-center mt-2">
+                        <span>{{call.callInfo.name || 'Unknown'}}</span>
+                    </div>
 
-                <div class="d-flex flex-row justify-content-center mt-5">
-                    <h2 v-if="callWidget.callInfo.time_limit">{{callWidget.callInfo.time_limit}}</h2>
-                </div>
+                    <div class="d-flex flex-row justify-content-center mt-5">
+                        <h2 v-if="call.callInfo.time_limit">{{call.callInfo.time_limit}}</h2>
+                    </div>
 
-                <div class="d-flex flex-row justify-content-between mt-5 ctrlBtns">
-                    <span @click="handleCall(true)">
-                        Accept
-                    </span>
-                    /
-                    <span @click="handleCall(false)">
-                        Reject
-                    </span>
+                    <div class="d-flex flex-row justify-content-between mt-5 ctrlBtns">
+                        <span @click="handleCall(call,true)">Accept</span>
+                        /
+                        <span @click="handleCall(call,false)">Reject</span>
+                    </div>
                 </div>
             </div>
-        </div>
-    </v-btn>
+        </v-btn>
+    </div>
 </template>
 
 <script>
-import { mapState } from "vuex"
+import { mapState } from "vuex";
 
 export default {
-    data(){
+    data() {
         return {
-            showCalleeDetails: false
-        }
+            showCalleeDetailsUid: null
+        };
     },
     computed: {
-        ...mapState(["lobbies","settings", "callWidget"]),
+        ...mapState(["lobbies", "settings", "callWidget", "callToNoAnswer"])
     },
     watch: {
-        'callWidget.active': {
-            handler(newVal){
-                if(!newVal){
-                    let self = this
-
-                    setTimeout(() => {
-                        self.showCalleeDetails = false
-                    }, 750)
-                }
-            }
+        // "callWidget.active": {
+        //     handler(newVal) {
+        //         if (!newVal) {
+        //             let self = this;
+        //             setTimeout(() => {
+        //                 self.showCalleeDetails = false;
+        //             }, 750);
+        //         }
+        //     }
+        // }
+        callToNoAnswer(newVal) {
+            this.$socket.emit("callDecision", {
+                uid: newVal.callInfo.uid,
+                accepted: false,
+                ring_uid: newVal.callInfo.ring_uid,
+                lobby_call_uid: newVal.callInfo.lobby_call_uid
+            });
         }
     },
     methods: {
-        handleCall(acceptCall) {
-            if(acceptCall){
-                this.$socket.emit('callDecision', true)
-                let lobby_data = {}
-                let meeting = []
-
-                for(let lobby of this.lobbies){
-                    meeting = lobby.meetings.filter((x) => x.uid == this.callWidget.callInfo.meeting_uid)
-
-                    if(meeting.length){
-                        lobby_data = {
-                            name: lobby.name,
-                            uid: lobby.uid
-                        }
-                        break
-                    }
-                }
+        handleCall(call, acceptCall) {
+            if (acceptCall) {
+                this.$socket.emit("callDecision", {
+                    uid: call.callInfo.uid,
+                    accepted: true,
+                    ring_uid: call.callInfo.ring_uid,
+                    lobby_call_uid: call.callInfo.lobby_call_uid
+                });
 
-                if(!meeting.length){
-                    return
+                if (call.callInfo.ring_uid) {
+                    $.ajax({
+                        url: "/post-to-api-ajax",
+                        method: "POST",
+                        headers: {
+                            "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
+                        },
+                        data: {
+                            _api: "/api/meetingRing/ringAnswer",
+                            uid: call.callInfo.ring_uid
+                        },
+                        success: data => {
+                            if (!data.success) {
+                                alert("Something went wrong.");
+                                console.error(data.message);
+                                return;
+                            }
+                            const joinData = {
+                                uid: null,
+                                name: null,
+                                selected_meeting: call.callInfo.meeting
+                            };
+
+                            this.$eventBus.$emit("joinMeeting", joinData);
+                            this.$store.commit("rejectCall", call);
+                        },
+                        error: jXhr => console.log(jXhr)
+                    });
                 } else {
-                    let data = {
-                        uid: lobby_data.uid,
-                        name: lobby_data.name,
-                        selected_meeting: meeting[0]
+                    let lobby_data = {};
+                    let meeting = [];
+                    let data = {};
+
+                    for (let lobby of this.lobbies) {
+                        meeting = lobby.meetings.filter(x => x.uid == call.callInfo.meeting_uid);
+
+                        if (meeting.length) {
+                            lobby_data = {
+                                name: lobby.name,
+                                uid: lobby.uid
+                            };
+                            break;
+                        }
                     }
 
-                    this.$eventBus.$emit('joinMeeting', data)
+                    if (!meeting.length) {
+                        return;
+                    } else {
+                        data = {
+                            uid: lobby_data.uid,
+                            name: lobby_data.name,
+                            selected_meeting: meeting[0]
+                        };
+                    }
+                    this.$eventBus.$emit("joinMeeting", data);
+                    this.$store.commit("rejectCall", call);
                 }
             } else {
-                this.$socket.emit('callDecision', false)
-                this.$store.commit('rejectCall')
+                this.$socket.emit("callDecision", {
+                    uid: call.callInfo.uid,
+                    accepted: false,
+                    ring_uid: call.callInfo.ring_uid,
+                    lobby_call_uid: call.callInfo.lobby_call_uid
+                });
+
+                if (call.callInfo.ring_uid) {
+                    $.ajax({
+                        url: "/post-to-api-ajax",
+                        method: "POST",
+                        headers: {
+                            "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content")
+                        },
+                        data: {
+                            _api: "/api/meetingRing/ringReject",
+                            uid: call.callInfo.ring_uid
+                        },
+                        error: jXhr => console.log(jXhr)
+                    });
+                }
+                this.$store.commit("rejectCall", call);
             }
         },
-        shiftForm(){
-            if(!this.settings.newMeetingNotificationExpanded){
-                this.showCalleeDetails = !this.showCalleeDetails
+        shiftForm(call) {
+            console.log(call);
+            if (!this.settings.newMeetingNotificationExpanded) {
+                if (this.showCalleeDetailsUid == call.uid) this.showCalleeDetailsUid = null;
+                else this.showCalleeDetailsUid = call.uid;
+                // this.showCalleeDetails = !this.showCalleeDetails;
             } else {
-                this.showCalleeDetails = true
+                this.showCalleeDetailsUid = call.uid;
             }
         }
     },
-    created(){
-        if(this.settings.newMeetingNotificationExpanded){
-            this.showCalleeDetails = true
-        }
+    created() {
+        // if (this.settings.newMeetingNotificationExpanded) {
+        //     this.showCalleeDetails = true;
+        // }
     }
-}
+};
 </script>
 
 <style scoped lang="scss">
-
-    @keyframes Glowing {
-        0% {
-            opacity: 0.3;
-        }
-        30% {
-            opacity: 0.8;
-        }
-        100% {
-            opacity: 1;
-        }
+@keyframes Glowing {
+    0% {
+        opacity: 0.3;
     }
+    30% {
+        opacity: 0.8;
+    }
+    100% {
+        opacity: 1;
+    }
+}
 
-    .callBtn {
-        z-index: 1000;
-        border-radius: 28px;
-        height: 56px !important;
-        width: 56px !important;
-        min-width: 56px !important;
-        overflow-x: hidden;
-        overflow-y: hidden;
-
-        transition: all 0.5s;
+.ring-container {
+    position: absolute;
+    display: flex;
+    flex-direction: column;
+    right: 16px;
+    bottom: 16px;
+}
 
-        transform: translateX(25vw);
+.callBtn {
+    z-index: 1000;
+    border-radius: 28px;
+    height: 56px !important;
+    width: 56px !important;
+    min-width: 56px !important;
+    overflow-x: hidden;
+    overflow-y: hidden;
+    margin-top: 5px;
 
-        .btnHeader {
-            animation: 0.7s Glowing infinite alternate ease-out;
-        }
+    transition: all 0.5s;
 
-        &.active {
-            transform: translateX(0);
-        }
+    transform: translateX(25vw);
 
-        .incomingCallMsg {
-            transition: all 0.3s;
-            display: none;
-            opacity: 0;
-        }
+    .btnHeader {
+        animation: 0.7s Glowing infinite alternate ease-out;
+    }
 
-        &:hover, &.showingAll {
-            width: 240px !important;
+    &.active {
+        transform: translateX(0);
+    }
 
-            .incomingCallMsg {
-                display: block;
-                opacity: 1;
-            }
-        }
+    .incomingCallMsg {
+        transition: all 0.3s;
+        display: none;
+        opacity: 0;
+    }
 
-        /* Full State */
+    &:hover,
+    &.showingAll {
+        width: 240px !important;
 
-        .fullDetails {
-            .ctrlBtns span {
-                &:hover {
-                    text-decoration: underline;
-                }
-            }
+        .incomingCallMsg {
+            display: block;
+            opacity: 1;
         }
+    }
 
-        &.showingAll {
-            height: 400px !important;
-        }
+    /* Full State */
 
-        &:not(.showingAll) {
-            .fullDetails {
-                display: none !important;
+    .fullDetails {
+        .ctrlBtns span {
+            &:hover {
+                text-decoration: underline;
             }
         }
+    }
 
+    &.showingAll {
+        height: 400px !important;
     }
 
+    &:not(.showingAll) {
+        .fullDetails {
+            display: none !important;
+        }
+    }
+}
 </style>

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

@@ -3,7 +3,6 @@
 <v-app id="meetingsApp" class="client">
     <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>

+ 10 - 0
resources/views/client/meeting.blade.php

@@ -0,0 +1,10 @@
+@extends('layouts.client')
+@section('content')
+<v-app id="meetingsApp" class="client">
+    <client-meeting 
+        :lobby-prop="{!! str_replace('"','\'',str_replace('\'','\\\'',json_encode($lobbyModel))) !!}"
+        :meeting-prop="{!! str_replace('"','\'',str_replace('\'','\\\'',json_encode($meeting))) !!}"
+        client-uid="{{ $sessionKey }}"
+    ></client-meeting>
+</v-app>
+@endsection

+ 166 - 1
resources/views/pro/clients_SINGLE/SUB_dashboard.blade.php

@@ -2,6 +2,171 @@
 @extends('pro.clients.view')
 @section('content-inner')
 
-    <h5 class='py-3 border-bottom'>Dashboard</h5>Controller: <b>clients_SINGLE</b><br>Action: <b>SUB_dashboard()</b><br>View: <b>pro/clients_SINGLE/SUB_dashboard.blade.php</b><br><br>
+<div class="container-fluid">
+    <div class="row">
+        <div class="col-md-12">
+            <img class="img-thumbnail" src="" alt="profile picture">
+            <p>{{$record->name_display()}}</p>
+            @if($record->is_duplicate)
+                <div>
+                    <p> This client is a duplicate of 
+                    <a href="/clients/view/{{$record->duplicateOfClient->uid}}">{{$record->duplicateOfClient->name_display()}}</a>. 
+                    <a up-modal=".form-contents" up-width="800" up-history="false" href="/clients/view/8340f0ac-bd01-4744-af16-f5cabcce14f8/ACTION_setIsDuplicateToFalse" title="Set as not duplicate">✏️ </a></p>
+                </div>
+            @endif
+            <hr/>
+                -------------------------------------
+                
+                
+                <Thumbnail>(1) <John Smith>(2) ✏️ @hover: full name
+                                                   ❌✅ (7)
+                <div>
+                    <img class="img-thumbnail" src="" alt="profile picture">
+                    <p>{{$record->name_display()}}</p>
+                </div>
+                
+                📞 {validated_cell_number} (4) 
+                <div><i class="fa fa-phone" aria-hidden="true"></i> {{$record->cell_number}}</div>
+                    ✉️ {validated_email} (5) 📌 {home_address.city}, {home_address.state}(6)
+                <div><i class="fa fa-envelope" aria-hidden="true"></i> {{$record->email_address}}</div>
+                <div><i class="fa fa-map-pin" aria-hidden="true"></i> {{$record->home_address_city}}</div>
+                <div><i class="fa fa-map-pin" aria-hidden="true"></i> {{$record->home_address_state}}</div>
+                <small> {gender}, {age calc:dob}
+                <div>
+                    {{$record->gender_identity}}, {{$record->sex}}, {{$record->dob}}, {{$record->age_in_years}}
+                </div>
+                
+                <small> Currently under the care of {MCP-First MCP-Last}✏️, with us since {creation.month}, {creation.date} || '8 days'}.
+                @if($record->mcpPro)
+                <div>
+                    <p>Currently under the care of {{$record->mcpPro->name_display}}</p>
+                    <a href=""><i class="fa fa-pencil" aria-hidden="true"></i></a>    
+                </div>
+                @else 
+                    <div>
+                        <p>No mcp pro assigned.</p>
+                        <a href=""><i class="fa fa-pencil" aria-hidden="true"></i></a>
+                    </div>
+                @endif
+
+                { If !medicare_valid || !mcp || !onboarded }
+                @if(!$record->is_mcn_valid_number || !$record->mcpPro || !$record->has_mcp_done_onboarding_visit)
+
+                    @if(!$record->is_mcn_valid_number)
+                    <div>
+                        { If !medicare_valid }
+                            <_____DOB Input_____>
+                            <_____Medicare Input_____>
+                            [Validate]
+                        MCN: {{$client->mcn}}
+                        <a href=""><i class="fa fa-pencil" aria-hidden="true"></i></a>
+                    </div>
+                    @elseif($record->is_mcn_valid_number || !$record->mcpPro )
+                        { Else if medicare_valid && !mcp }
+                        Please assign a MCP to this client.
+                        <_____Text or Email_____> [Invite]
+                        <Dropdown/Datalist of MCPs> [Assign]
+                        <a href="">Assign mcp pro</a>
+                    @elseif($record->is_mcn_number_valid && !$record->has_mcp_done_onboarding_visit)
+                        { Else if medicare_valid && !onboarded }
+                         This client hasn't been onboarded yet.   
+                        <div class="alert alert-info">This client hasn't been onboarded yet.</div>
+                    @endif
+                @endif
+                { Else onboarded_already }
+                
+                    <main>
+                        Care Monitoring:
+                            { If !cmPro } <Dropdown/Datalist of possible CMs>
+                            {else}  <link>{cmPro}</link> ✏️
+                
+                            { If cmPro }
+                                { If !cm} Enroll this client into CM.
+                                { Else }
+                                    <small> Unenroll out of CM.
+                                        @link _____why?_____ [Unenroll]
+                
+                        Remote Monitoring:
+                            { If !rmePro } <Dropdown/Datalist of possible RMEs>
+                            { Else }  <link>{rmePro}</link> ✏️
+                
+                            { If !rmmPro} <Dropdown/Datalist of possible RMMs>
+                            {else}  <link>{rmmPro}</link> ✏️
+                            
+                            { If rmePro && rmmPro}
+                                { If !rm} Enroll this client into RM.
+                                {else}
+                                    <small> Unenroll out of RM.
+                                        @link _____why?_____ [Unenroll]
+                
+                
+                        << Prev     Care Months - Month, Year     Next >> (max, one month in future)
+                        { If !careMonth}
+                            {mcp} ✏️
+                            [Add a care month]
+                
+                        {else if tm}
+                            ⚠️ Patient recently discharged, transitional care management billable for this month.
+                
+                        {else !tm && (cm || rm)}
+                            Eligible for CM_20/30_HCP/90. RM_20/30/90. {calc: careMonth.totalTimeCM / careMonth.totalTimeRM}
+                            Total time billed this month: {careMonth.totalTimeCM} minutes in CM, {careMonth.totalTimeRM} minutes in RM.
+                
+                            { If cm}
+                                {cmPro} ✏️
+                
+                            { If rm}
+                                {rmePro} ✏️
+                                {rmmPro} ✏️
+                
+                            [Add an entry]
+                            {Loop entries recent}
+                                Created | Type (rm/cm) | Duration | Content | Pro
+                            {/Loop entries}
+                    </main>
+                
+                
+                
+                
+                (6) @click: modal ✏️
+                    _____home.address.line1_____
+                    _____home.address.line2_____
+                    _____home.address.zip_____
+                    {Try to geocode zip}
+                    _____home.address.city_____
+                    _____home.address.state_____
+                    _____home.address.lat_____
+                    _____home.address.long_____
+                
+                    { If !mailing}
+                        [✔️] Use the home address.
+                        [Add a mailing address]
+                            _____mailing.address.line1_____
+                            _____mailing.address.line2_____
+                            _____mailing.address.zip_____
+                            {Try to geocode zip}
+                            _____mailing.address.city_____
+                            _____mailing.address.state_____
+                            _____mailing.address.lat_____
+                            _____mailing.address.long_____
+                
+                    { If !secondary}
+                        _____secondary_address.reason_____
+                        _____secondary_address.when_____
+                        [Add secondary]
+                
+                        <Use the same template as above>
+                
+                
+                (7) Medicare number can either be invalid, or empty, something which can't be validated: don't show anything.
+                    Or, it can be a valid medicare number, but for some reason is NO GOOD for us: show red cross.
+                    Or, it can be a valid medicare number, which is GOOD for us: show green tick.
+                
+                
+                (9) @click: modal
+                
+        </div>
+    </div>
+</div>
 
 @endsection

+ 4 - 3
routes/web.php

@@ -69,9 +69,10 @@ Route::post('/post-to-api-ajax', 'AppSessionController@postToAPIAjax')->name('po
 
 
 Route::middleware('ensureOnlyStrangerSession')->group(function(){
-	Route::get('/client/{url_slug}', 'ClientController@entranceLobby')->name('client-lobby');
+    Route::get('/client/{url_slug}', 'ClientController@entranceLobby')->name('client-lobby');
+    Route::get('/client/meeting-participant/{meetingParticipant:uid}', 'ClientController@meeting');
 });
-Route::get('/client/meeting/{meeting_uid}', 'ClientController@entranceLobby')->name('join-meeting');
+// Route::get('/client/meeting/{meeting_uid}', 'ClientController@entranceLobby')->name('join-meeting');
 
 Route::bind('url_slug', function($value, $route)
 {
@@ -85,4 +86,4 @@ Route::get('/section_update_form/{section_uid}', 'NoteController@sectionUpdateFo
 Route::post("/process_form_submit", 'NoteController@processFormSubmit')->name('process_form_submit');
 if (env('APP_ENV') === 'production') {
     URL::forceScheme('https');
-}
+}