GOOD SHELL MAS BOY
Server: Apache/2.4.52 (Ubuntu)
System: Linux vmi1836763.contaboserver.net 5.15.0-130-generic #140-Ubuntu SMP Wed Dec 18 17:59:53 UTC 2024 x86_64
User: www-data (33)
PHP: 8.4.10
Disabled: NONE
Upload Files
File: /var/www/html/resources/views/livewire/buyer/chat.blade.php
<div>

    <script type="module">
        import {
            initializeApp,
        } from "https://www.gstatic.com/firebasejs/9.17.2/firebase-app.js";

        import {
            getFirestore,
            collection,
            query,
            where,
            onSnapshot,
            doc,
        } from "https://www.gstatic.com/firebasejs/9.17.2/firebase-firestore.js";

        import {
            getMessaging
        } from "https://www.gstatic.com/firebasejs/11.1.0/firebase-messaging.js";


        // Firebase Configuration
        const firebaseConfig = {
            apiKey: "AIzaSyBttDksOxuahV-SCex9ho2SpHrYeSbwnj4",
            authDomain: "fixgini.firebaseapp.com",
            projectId: "fixgini",
            storageBucket: "fixgini.appspot.com",
            messagingSenderId: "994362424638",
            appId: "1:994362424638:web:9b0a92ba6c61ecabd6006f",
            measurementId: "G-ECEEH8CQVH",
        };

        // Initialize Firebase and Firestore
        const app = initializeApp(firebaseConfig);
        const db = getFirestore(app);

        const authenticatedUserUID = "{{$user_id}}"; // Pass this dynamically from the backend

        window.renderMessages = renderMessages;



        // Update renderMessages function to filter chats involving the authenticated user
        async function renderMessages() {
            try {
                const chatsRef = collection(db, "chats");
                const q = query(chatsRef, where("participantsUIDs", "array-contains", authenticatedUserUID));

                onSnapshot(q, (snapshot) => {
                    const allChats = [];
                    const engagedChats = [];
                    const completedChats = [];

                    snapshot.forEach((chatDoc) => {
                        const chatData = chatDoc.data();
                        const chatRoomStatus = chatData.chat_room_status;
                        const participantsUIDs = chatData.participantsUIDs;

                        // Ensure the authenticated user is part of the chat
                        if (participantsUIDs.includes(authenticatedUserUID)) {
                            if (chatRoomStatus === "active") {
                                engagedChats.push(chatData);
                            } else if (chatRoomStatus === "completed") {
                                completedChats.push(chatData);
                            } else {
                                allChats.push(chatData);
                            }
                        }
                    });

                    // Render All and Engaged chats dynamically
                    renderChatList("all", allChats);
                    renderChatList("engaged", engagedChats);
                    renderChatList("completed", completedChats);
                });

            } catch (error) {
                console.error("Error rendering messages:", error);
            }
        }

        // Function to render chat list
        function renderChatList(tab, chats) {
            const chatListContainer = document.getElementById(tab);
            chatListContainer.innerHTML = ""; // Clear old chat list

            const groupedChats = {};
            chats.sort((a, b) => new Date(b.lastMessageAt) - new Date(a.lastMessageAt));

            chats.forEach((chat) => {
                const participantsUIDs = chat.participantsUIDs;

                // Find the other participant (not the authenticated user)
                const otherParticipantUID = participantsUIDs.find(uid => uid !== authenticatedUserUID);

                if (!otherParticipantUID) {
                    console.warn("No other participant found for chat:", chat.chatId);
                    return;
                }

                const otherParticipant = chat.participants.find(p => p.userUID === otherParticipantUID);

                if (otherParticipant) {
                    const participantPhoto = otherParticipant.userPhoto || "https://console.fixgini.com/icon.png";
                    const participantName = otherParticipant.firstName || "Unknown";
                    const lastMessage = chat.lastMessage || "No messages";
                    const lastMessageTruncated = lastMessage.length > 25 ? lastMessage.substring(0, 25) + '...' : lastMessage;

                    // Group chats by participants
                    if (!groupedChats[otherParticipantUID] || new Date(chat.lastMessageAt) > new Date(groupedChats[otherParticipantUID].lastMessageAt)) {
                        groupedChats[otherParticipantUID] = {
                            photo: participantPhoto,
                            name: participantName,
                            lastMessage: lastMessageTruncated,
                            chatId: chat.chatId,
                        };
                    }
                }
            });

            // Render the grouped chats
            if (Object.keys(groupedChats).length === 0) {
                const noChatMessage = document.createElement("div");
                noChatMessage.className = "no-chat-message text-center mt-1";
                noChatMessage.innerText = "No chat contact";
                chatListContainer.appendChild(noChatMessage);
            } else {
                Object.values(groupedChats).forEach(chat => {
                    console.log('incoming new chat');
                    const chatId = sessionStorage.getItem('chatId');
                    const photo = sessionStorage.getItem('photo');
                    const name = sessionStorage.getItem('name');

                    // Call the loadChatDetails function and wait for it to complete
                    loadChatDetails(chatId, photo, name);
                    window.renderMessages = renderMessages;

                    // can i use livewire refresh approach here
                    Swal.fire({
                        text: 'You have a new chat message.',
                        position: 'top-end', // Positioning at the top-right
                        showConfirmButton: false, // To show the confirm button
                        timer: 6000, // Auto close after 3 seconds
                        timerProgressBar: true, // Show a progress bar during the timer countdown
                        width: '300px', // Adjust the width to make it smaller
                        padding: '10px', // Reduce padding to make it more compact
                        customClass: {
                            popup: 'small-popup' // Custom class for further styling if needed
                        }
                    });

                    // Optionally add custom CSS for better styling
                    const style = document.createElement('style');
                    style.innerHTML = `
                            .small-popup {
                                font-size: 16px;
                                padding: 10px;
                            }
                        `;

                    const chatMemberElement = document.createElement("div");
                    chatMemberElement.className = "chat-member d-flex align-items-center mb-3";
                    chatMemberElement.style.borderBottom = "1px solid #cc6"; // Add bottom border
                    chatMemberElement.innerHTML = `
                        <img src="${chat.photo}" class="rounded-circle ml-3 mb-2" width="50" height="50" /> &nbsp;
                        <div class="ml-3">
                            <h6 class="mb-0">${chat.name}</h6>
                            <p class="mb-0 text-muted small">${chat.lastMessage}</p>
                        </div>
                    `;
                    chatMemberElement.addEventListener('click', async function() {
                        await loadChatDetails(chat.chatId, chat.photo, chat.name);
                    });
                    chatListContainer.appendChild(chatMemberElement);
                });
            }
        }


        async function loadChatDetails(chatId, photo, name) {
            console.log('loading new chat');
            const audio = new Audio('https://fixgini.com/notification.wav'); // Replace with your sound file URL
            // Play the audio
            audio.play().catch((error) => {
                console.error("Error playing the notification sound: ", error);
            });
            sessionStorage.setItem('chatId', chatId);
            sessionStorage.setItem('photo', photo);
            sessionStorage.setItem('name', name);

            try {
                const userHeading = document.getElementById("user-heading");
                if (name == null) {
                    userHeading.innerHTML = ''; // Clear the content if name or photo is null/empty
                } else {
                    userHeading.innerHTML = `
                    <img src="${photo}" class="rounded-circle" width="50" height="50" />
                    <div class="ml-3">
                        <h6 class="mb-0">${name}</h6>
                        <span class="text-success small">Online</span>
                    </div>
                    `;
                }


                const chatMessagesList = document.getElementById("chat-messages-list");
                chatMessagesList.innerHTML = "";

                const chatMessages = await fetchChatRoomDetails(chatId);
                let showActionButton = false;
                let showCompleteButton = false;
                let paymentLink = null;
                let amountToPay = '';
                let gigCurrency = '';
                let gigName = '';
                let gigId = '';
                let bookingId = '';

                if (!chatMessages || chatMessages.length === 0) {
                    if (name == null) {
                        chatMessagesList.innerHTML = `<p class="text-muted text-center">No messages yet</p>`;
                    } else {
                        chatMessagesList.innerHTML = `<p class="text-muted text-center">Select chat to see conversation</p>`;

                    }
                } else {
                    chatMessages.sort((a, b) => new Date(a.time) - new Date(b.time));
                    chatMessages.forEach(chatMessage => {
                        const chatDate = new Date(chatMessage.time);
                        const currentDate = new Date();

                        const authUserId = "{{$user_id}}";
                        // Check if action button should be shown to customer or provider, so it only proivder to acept it should show to
                        if (chatMessage.customerId != authUserId) {
                            if (chatMessage.showActionButton) {
                                showActionButton = true;
                                amountToPay = chatMessage.amount || ''; // Assign the value from chatMessage
                                gigCurrency = chatMessage.gigCurrency || ''; // Assign the value from chatMessage
                                gigName = chatMessage.gigName || ''; // Assign the value from chatMessage
                                gigId = chatMessage.gigId || ''; // Assign the value from chatMessage
                                bookingId = chatMessage.bookingId || ''; // Assign the value from chatMessage
                            }
                        }
                        // only customer should see the payment link
                        if (chatMessage.customerId === authUserId) {
                            if (chatMessage.paymentLink) {
                                paymentLink = chatMessage.paymentLink; // Update the payment link if found
                            }
                        }
                        // only customer should see the completion button
                        if (chatMessage.customerId === authUserId) {
                            if (chatMessage.showCompleteButton) {
                                showCompleteButton = true;
                                gigName = chatMessage.gigName || ''; // Assign the value from chatMessage
                                gigId = chatMessage.gigId || ''; // Assign the value from chatMessage
                                bookingId = chatMessage.bookingId || ''; // Assign the value from chatMessage
                            }
                        }

                        // Function to format the date as requested
                        function formatDate(date) {
                            const today = new Date();
                            const dayOfWeek = date.toLocaleString('en-US', {
                                weekday: 'short'
                            });
                            const options = {
                                hour: '2-digit',
                                minute: '2-digit',
                                hour12: true
                            };

                            // Check if the date is today
                            if (date.toDateString() === today.toDateString()) {
                                return date.toLocaleString('en-US', options); // Just show time like 04:12 PM
                            }

                            // Check if the date is within the same week
                            const diffInDays = Math.floor((today - date) / (1000 * 60 * 60 * 24)); // Difference in days
                            if (diffInDays < 7) {
                                return `${dayOfWeek} ${date.toLocaleString('en-US', options)}`; // Show day and time like Thurs 04:56 PM
                            }

                            // If the date is older than a week
                            return date.toLocaleString('en-GB', {
                                day: '2-digit',
                                month: '2-digit',
                                year: 'numeric',
                                hour: '2-digit',
                                minute: '2-digit',
                                hour12: true
                            }); // Show date and time like 16/04/2025 06:56 AM
                        }

                        const formattedTime = formatDate(chatDate);

                        const messageElement = document.createElement("div");
                        const isSender = chatMessage.sentBy === "{{$user_id}}"; // Compare the sender with the auth user ID

                        messageElement.className = `message mb-3 d-flex justify-content-${isSender ? "end" : "start"}`;
                        messageElement.innerHTML = `
                            <div class="p-2 ${isSender ? "text-primary btn-light-thm rounded-end" : "text-white bg-primary rounded-start"}" style="border-radius: 25px">
                                ${chatMessage.message || "No content"} 
                                <small class="d-block mt-1 text-end ${isSender ? "text-primary" : "text-white"}" style="font-size: 12px;">
                                    ${formattedTime}
                                </small>
                            </div>
                        `;
                        chatMessagesList.appendChild(messageElement);
                    });
                    // Update the UI for action button and payment link
                    const actionButtonContainer = document.getElementById("action-button-container");
                    const paymentLinkContainer = document.getElementById("payment-link-container");
                    const showCompleteButtonContainer = document.getElementById("show-complete-button-container");

                    if (showCompleteButton) {
                        showCompleteButtonContainer.innerHTML = `
                             <p>Provider await your confirmation for completion - <strong>${gigName || ''}</strong> </p>
                            <button id="confirm-button" class="btn btn-success text-white mr-2 fw-bold">
                                Confirm Completion
                            </button> 
                        `;
                        // Attach event listeners after the buttons are added to the DOM
                        document.getElementById("confirm-button").addEventListener("click", function() {
                            handleConfirmAction(bookingId);
                        });
                    } else {
                        showCompleteButtonContainer.innerHTML = ` 
                        `;
                    }

                    if (showActionButton) {
                        actionButtonContainer.innerHTML = `
                        <p>Customer wants to engage your <strong>${gigName}, for ${gigCurrency} ${amountToPay || 'N/A'}</strong> </p>
                            <button id="accept-button" class="btn btn-success text-white mr-2 fw-bold">
                                Accept
                            </button>
                            <button id="decline-button" class="btn btn-danger text-white fw-bold">
                                Decline
                            </button>
                        `;
                        // Attach event listeners after the buttons are added to the DOM
                        document.getElementById("accept-button").addEventListener("click", function() {
                            handleAcceptAction(gigId, bookingId);
                        });

                        document.getElementById("decline-button").addEventListener("click", function() {
                            handleDeclineAction(gigId, bookingId);
                        });
                    } else {
                        actionButtonContainer.innerHTML = `
                        `;

                    }

                    if (paymentLink) {
                        paymentLinkContainer.innerHTML = `
                            <a href="${paymentLink}" target="_blank" class="btn btn-light-thm text-primary">
                                Make Payment
                            </a>
                        `;
                    } else {
                        paymentLinkContainer.innerHTML = ` 
                        `;
                    }

                }
            } catch (error) {
                console.error("Error loading chat details:", error);
            }
        }

        async function fetchChatRoomDetails(chatId) {
            try {
                sessionStorage.setItem('chatId', chatId);
                const response = await fetch(`https://firestore.googleapis.com/v1/projects/fixgini/databases/(default)/documents/chats/${chatId}/chat_room`);
                const data = await response.json();

                // Extract chat room details
                if (data.documents) {
                    const chatMessages = data.documents.map(doc => {
                        try {
                            // Extract values from fields
                            const fields = doc.fields || {};
                            const sentBy = fields.sentBy?.stringValue || null;
                            const message = fields.message?.stringValue || "";
                            const time = fields.time?.stringValue || null;
                            const paymentLink = fields.paymentLink?.stringValue || null;

                            // Extract participants array to get customer and provider IDs
                            const participants = fields.participants?.arrayValue?.values || [];
                            const customerId = participants[0]?.stringValue || null; // First value is customer
                            const providerId = participants[1]?.stringValue || null; // Second value is provider

                            // Extract chatHire or webHire details
                            // Extract chatHire or webHire details
                            const hireDetails = fields.webHire?.mapValue?.fields || fields.chatHire?.mapValue?.fields || {};
                            const accepted = hireDetails.accepted?.booleanValue ?? null; // Ensure to access booleanValue
                            const confirmCompleted = hireDetails.confirmCompleted?.booleanValue ?? null; // Ensure to access booleanValue
                            const completed = hireDetails.completed?.booleanValue ?? null; // Ensure to access booleanValue
                            const paid = hireDetails.paid?.booleanValue ?? null; // Ensure to access booleanValue
                            const amount = hireDetails.price?.doubleValue ?? '';
                            const gigCurrency = hireDetails.gigCurrency?.stringValue ?? '';
                            const gigName = hireDetails.gigName?.stringValue ?? '';
                            const gigId = hireDetails.gigId?.stringValue ?? '';
                            const bookingId = hireDetails.bookingId?.stringValue ?? '';

                            // Set the bookingId session
                            if (bookingId) {
                                sessionStorage.setItem('bookingId', bookingId);
                            }

                            // Determine if the action button should be shown
                            const showActionButton = accepted === false && paid === false;
                            const showCompleteButton = completed === true && confirmCompleted === false;
                            // const showActionButton = accepted === false && paid === false && paymentLink === null;

                            return {
                                sentBy,
                                amount,
                                gigCurrency,
                                gigName,
                                gigId,
                                bookingId,
                                customerId,
                                providerId,
                                message,
                                time,
                                paymentLink,
                                showActionButton,
                                showCompleteButton,
                            };
                        } catch (error) {
                            console.error("Error processing document:", error, doc);
                            return null; // Return null if there's an error processing this document
                        }
                    }).filter(Boolean); // Remove null entries caused by errors

                    return chatMessages; // Return all messages
                }

                return null;
            } catch (error) {
                console.error("Error fetching chat room details:", error);
                return null;
            }
        }


        // Call renderMessages on page load
        renderMessages();

        // Function to send a message to Firestore
        async function sendMessage(message) {
            if (!message || message.trim() === "") {
                alert("Message cannot be empty");
                console.log("Message cannot be empty.");
                return;
            }
            console.log('Sending message to backend and fcm notification');

            const bearerToken = "{{$userBearToken}}"; // Assuming this is properly injected on the page
            const bookingId = sessionStorage.getItem('bookingId');

            const apiUrl = 'https://console.fixgini.com/api/v1/chat-message';

            fetch(apiUrl, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                        'Authorization': 'Bearer ' + bearerToken, // Corrected concatenation
                    },
                    body: JSON.stringify({
                        message: message, // Sending message data in the body
                        booking_id: bookingId, // Sending bookingId data in the body
                    }),
                })
                .then(response => {
                    if (!response.ok) { // Check if the status is not within the range 200–299
                        throw new Error(`Error sending notification! status: ${response.message}`);
                    }
                    return response.json(); // Parse JSON only if the status is OK
                })
                .then(data => {
                    console.log("Notification sent successfully:", data);
                    // Process notification here 
                    document.getElementById('messageInput').value = "";
                    const chatId = sessionStorage.getItem('chatId');
                    const photo = sessionStorage.getItem('photo');
                    const name = sessionStorage.getItem('name');

                    // Call the loadChatDetails function and wait for it to complete
                    loadChatDetails(chatId, photo, name);
                    window.renderMessages = renderMessages;
                })
                .catch(error => {
                    console.error("Error sending notification:", error);
                });
        }

        // Attach event listener to the send message button
        document.getElementById('sendMessageBtn').addEventListener('click', function() {
            const message = document.getElementById('messageInput').value;
            sendMessage(message);
        });

        async function handleAcceptAction(gigId, bookingId) {
            const url = "https://console.fixgini.com/api/v1/booking-action";
            const token = "{{$userBearToken}}"; // Replace with your actual token or dynamically retrieve it

            const body = {
                message: "Engagement accepted",
                action: "Accepted",
                gig_id: gigId,
                booking_id: bookingId,
            };

            const headers = {
                Authorization: `Bearer ${token}`,
                Accept: "application/json",
                "Content-Type": "application/json",
            };

            try {
                const response = await fetch(url, {
                    method: "POST",
                    headers: headers,
                    body: JSON.stringify(body),
                });

                const result = await response.json();

                if (response.ok) {
                    alert("Booking accepted successfully!");
                    // Optionally refresh the UI or perform further actions
                    // location.reload();
                } else {
                    alert(result.message || "An error occurred while accepting the booking.");
                }
            } catch (error) {
                console.error("Error accepting booking:", error);
                alert("An unexpected error occurred. Please try again.");
            }
        }


        async function handleDeclineAction(gigId, bookingId) {
            const url = "https://console.fixgini.com/api/v1/booking-action";
            const token = "{{$userBearToken}}";

            const body = {
                message: "Engagement declined",
                action: "declined",
                gig_id: gigId,
                booking_id: bookingId,
            };

            const headers = {
                Authorization: `Bearer ${token}`,
                Accept: "application/json",
                "Content-Type": "application/json",
            };

            try {
                const response = await fetch(url, {
                    method: "POST",
                    headers: headers,
                    body: JSON.stringify(body),
                });
                console.log('Decline response is ', response);

                const result = await response.json();

                if (response.ok) {
                    alert("Booking declined successfully.");
                    // Optionally refresh the UI or perform further actions
                    // location.reload();
                } else {
                    alert(result.message || "An error occurred while declining the booking.");
                }
            } catch (error) {
                console.error("Error declining booking:", error);
                alert("An unexpected error occurred. Please try again.");
            }
        }
        // Completion button
        async function handleConfirmAction(bookingId) {
            const url = "https://console.fixgini.com/api/v1/complete-booking";
            const token = "{{$userBearToken}}"; // Replace with your actual token or dynamically retrieve it

            const body = {
                message: "Engagement completion successfully",
                action: "Accepted",
                booking_id: bookingId,
            };

            console.log('body is ', body);

            const headers = {
                Authorization: `Bearer ${token}`,
                Accept: "application/json",
                "Content-Type": "application/json",
            };

            try {
                const response = await fetch(url, {
                    method: "POST",
                    headers: headers,
                    body: JSON.stringify(body),
                });

                const result = await response.json();

                if (response.ok) {
                    alert("Engagement confirm completed successfully!");
                    // Optionally refresh the UI or perform further actions
                    // location.reload();
                } else {
                    alert(result.message || "An error occurred while accepting the Engagement confirm completed.");
                }
            } catch (error) {
                console.error("Error accepting Engagement confirm completed:", error);
                alert("An unexpected error occurred. Please try again.");
            }
        }
        // For Confirm Action button
        document.getElementById("confirm-button").addEventListener("click", function() {
            handleConfirmAction(bookingId);
        });

        // For Accept Action button
        document.getElementById("accept-button").addEventListener("click", function() {
            handleAcceptAction(gigId, bookingId);
        });

        // For Decline Action button
        document.getElementById("decline-button").addEventListener("click", function() {
            handleDeclineAction(gigId, bookingId);
        });
    </script>

    <div class="row mb40">
        {{-- Chat List --}}
        <div class="col-lg-4 col-xl-4 col-xxl-4">
            <div class="message_container">
                <div class="d-flex justify-content-between align-items-center mb-3 mx-3 my-3">
                    <button class="btn btn-light-thm active rounded-pill">All</button>
                    <button hidden class="btn btn-light-thm rounded-pill" onclick="showStatusTab('engaged')">Engaged</button>
                    <button hidden class="btn btn-light-thm rounded-pill" onclick="showStatusTab('completed')">Completed</button>
                </div>

                <div class="inbox_user_list">
                    <div id="all" class="chat-member-list tab-content" style="max-height: 300px; overflow-y: auto;">
                        <!-- This will be populated dynamically with JavaScript -->
                    </div>

                    <div id="engaged" class="chat-member-list tab-content"
                        style="max-height: 300px; overflow-y: auto; display: none;">
                        <!-- This will be populated dynamically with JavaScript -->
                    </div>

                    <div id="completed" class="chat-member-list tab-content"
                        style="max-height: 300px; overflow-y: auto; display: none;">
                        <!-- This will be populated dynamically with JavaScript -->
                    </div>

                </div>
            </div>
        </div>


        <!-- Chat View -->
        <div class="col-lg-8 col-xl-8 col-xxl-8">
            <div class="message_container mt30-md">
                <!-- Chat Header -->
                <div id="user-heading" class="user_heading px-3 py-2 d-flex align-items-center">
                    <!-- Dynamic header content -->
                </div>

                <!-- Chat Messages -->
                <div class="inbox_chatting_box bg-light p-3" style="height: 400px; overflow-y: auto;">
                    <!-- Dynamic Chat Messages -->
                    <div id="chat-messages-list">
                        <!-- Messages will dynamically populate here -->
                    </div>
                    <!-- Action Buttons (Accept/Decline) -->
                    <div id="action-button-container" class="mt-3">
                        <!-- Accept and Decline buttons will be dynamically inserted here -->
                    </div>
                    <!-- Payment Link -->
                    <div id="payment-link-container" class="mt-3">
                        <!-- Payment link will be dynamically inserted here -->
                    </div>
                    <!-- Confirm Button -->
                    <div id="show-complete-button-container" class="mt-3">
                        <!-- Confirm Completion button will be dynamically inserted here -->
                    </div>
                </div>

                <!-- Message Input Field -->
                <div class="message_input_box d-flex align-items-center mt-3 mx-2 mb-2">
                    <input
                        type="text"
                        id="messageInput"
                        class="form-control flex-grow-1"
                        placeholder="Type a message..."
                        style="margin-right: 10px" />
                    <button
                        id="sendMessageBtn"
                        class="btn btn-primary text-white"
                        style="margin-right: 10px">
                        <i class="fas fa-paper-plane"></i>
                    </button>
                    <button hidden class="btn btn-outline-secondary text-muted">
                        <i class="fas fa-paperclip"></i>
                    </button>
                </div>
            </div>
        </div>

    </div>
</div>