diff --git a/src/App.jsx b/src/App.jsx index 89736f7..43ff713 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,4 +1,5 @@ import { useState, useEffect } from 'react' +import { createContext } from 'react' import reactLogo from './assets/react.svg' import viteLogo from '/vite.svg' import './App.css' @@ -8,15 +9,20 @@ import { API_BASE } from './config.js' import Login from './components/login.jsx' import MainApp from './components/MainApp.jsx' +export const ClientContext = createContext(null); + +export function userContext() {} export default function App() { const [loading, setLoading] = useState(true); // true while checking auth const [loggedIn, setLoggedIn] = useState(false); + const [clientId, setClientId] = useState([]); useEffect(() => { async function checkAuth() { try { // This endpoint should validate the refresh token cookie and return an access token + const res = await fetch(`${API_BASE}/auth/login_refresh_token`, { method: "POST", headers: { @@ -28,7 +34,9 @@ export default function App() { device_id : "147ac10b-58cc-4372-a567-0e02b2c3d479", // or persistent device ID }), }); + console.log("sent checkAuth request") + if (!res.ok) { console.log(res) console.log("checkAuth request not ok") @@ -40,6 +48,8 @@ export default function App() { const data = await res.json(); console.log(data) localStorage.setItem("access_tokens", JSON.stringify(data.tokens)); // store for API calls + setClientId(data.user_id) + setLoggedIn(true); } } catch (err) { @@ -57,7 +67,7 @@ export default function App() { // Show main app if logged in, otherwise show login return loggedIn ? ( - + ) : ( setLoggedIn(true)} /> ); diff --git a/src/components/HotelContext.jsx b/src/components/HotelContext.jsx index 7c7c699..264912a 100644 --- a/src/components/HotelContext.jsx +++ b/src/components/HotelContext.jsx @@ -7,14 +7,18 @@ export function useHotel() { return useContext(HotelContext); } -export function HotelProvider({ accessToken, children }) { +export function HotelProvider({ accessToken, children, resClientId}) { const [rooms, setRooms] = useState([]); const [conversations, setConversations] = useState([]); + const [users, setUsers] = useState([]); + const [usersById, setUsersById] = useState([]); + const [clientId, setClientId] = useState(resClientId|| null); const tokens = JSON.parse(accessToken); const accessTokenSingle = tokens[0]; + // --- API FUNCTIONS --- async function fetchRooms() { const res = await fetch( `${API_BASE}/rooms/rooms`, { @@ -46,7 +50,17 @@ export function HotelProvider({ accessToken, children }) { "Content-Type": "application/json", }, }); - return res.json(); + + const users = await res.json(); + + const map = {}; + for (const u of users) { + map[u.id] = u.username; + } + setUsersById(map); + +// setUsers(users) + return users; } async function fetchConversations() { @@ -71,7 +85,7 @@ export function HotelProvider({ accessToken, children }) { timestamp: "2025-09-25 11:05:33", }; - console.log(JSON.stringify(payload)); + //console.log(JSON.stringify(payload)); const res = await fetch(`${API_BASE}/chat/get_message`, { method: "POST", @@ -87,15 +101,42 @@ export function HotelProvider({ accessToken, children }) { //return res.json } +async function fetchConvUsers({ conv_id }) { + + const payload = { + conv_id, + timestamp: "2025-09-25 11:05:33", + }; + + //console.log(JSON.stringify(payload)); + + const res = await fetch(`${API_BASE}/chat/get_conv_users/${conv_id}`, { + method: "POST", + headers: { + Authorization: `Bearer ${accessTokenSingle}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + }); + + return res.json(); + + //return res.json + } + + async function sendMessage({ conv_id, message }) { - if (!conv_id) return; - console.log("conv_id null at sendMessage") + if (!conv_id) { + console.log("conv_id null at sendMessage") + return + }; + const payload = { conv_id, message }; - console.log(JSON.stringify(payload)); + //console.log(JSON.stringify(payload)); const res = await fetch(`${API_BASE}/chat/send_message`, { method: "POST" , @@ -107,6 +148,27 @@ export function HotelProvider({ accessToken, children }) { }) } + async function addUserToConv({ conv_id, users}) { + if (!conv_id || users) { + console.log("error in convs or user to add") + }; + + const payload = { + conv_id, + users + }; + + const res = await fetch(`${API_BASE}/chat/add_users_conv`, { + method: "PUT" , + headers: { + Authorization: `Bearer ${accessTokenSingle}`, + "Content-Type" : "application/json", + }, + body: JSON.stringify(payload), + }) + + + } // --- INITIAL DATA LOADING --- useEffect(() => { @@ -119,12 +181,15 @@ export function HotelProvider({ accessToken, children }) { fetchHotelUsers(), ]); + setClientId(resClientId); setRooms(roomsData); setConversations(convData); + //console.log("USERS =",users) setUsers(usersData); } - + load(); + //console.log("USERS 2 =",usersById) }, [accessToken]); return ( @@ -133,9 +198,14 @@ export function HotelProvider({ accessToken, children }) { rooms, conversations, users, + usersById, + clientId, updateRoomStatus, fetchMessages, + fetchConvUsers, sendMessage, + fetchHotelUsers, + addUserToConv, }} > {children} diff --git a/src/components/MainApp.jsx b/src/components/MainApp.jsx index 46dfbc4..28b4458 100644 --- a/src/components/MainApp.jsx +++ b/src/components/MainApp.jsx @@ -4,11 +4,11 @@ import ChatWidget from "./widget/chatWidget" import "./MainApp.css" -export default function MainAppWrapper() { +export default function MainAppWrapper({resClientId}) { const accessToken = localStorage.getItem("access_tokens"); return ( - + ); @@ -37,7 +37,4 @@ function MainApp() { ); - - - } diff --git a/src/components/widget/RoomWidget.css b/src/components/widget/RoomWidget.css index fd4d341..385aeb6 100644 --- a/src/components/widget/RoomWidget.css +++ b/src/components/widget/RoomWidget.css @@ -1,3 +1,21 @@ +.messagebox{ + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +/* your messages */ +.msg--mine { + align-self: flex-end; + background-color: #464b43; + text-align: right; +} + +/* others */ +.msg--theirs { + align-self: flex-start; + background-color: #473f3f; +} .grid { display: flex; justify-items: space-around; diff --git a/src/components/widget/chatWidget.jsx b/src/components/widget/chatWidget.jsx index 5d5e26f..57e4601 100644 --- a/src/components/widget/chatWidget.jsx +++ b/src/components/widget/chatWidget.jsx @@ -1,23 +1,27 @@ import { useHotel } from "../HotelContext"; -import { useState } from "react"; +import { useState, useEffect } from "react"; +import { useRef } from "react"; //import {fetchMessage} from . import "./chatWidget.css" export default function ChatWidget({convlist}) { - const [messages, setMessages] = useState([]); - const { fetchMessages, sendMessage } = useHotel(); - const [activeConvId, setActiveConvId] = useState(null); + const { fetchMessages, sendMessage, + usersById, clientId, fetchConvUsers, + addUserToConv + } = useHotel(); + const [activeConvId, setActiveConvId] = useState(null); + const [showAddUsers, setShowAddUsers] = useState(false); const handleOpenConv = async (conv_id) => { setActiveConvId(conv_id); - const msg = await fetchMessages({ conv_id }); - setMessages(msg); }; - return ( + console.log("client id in chat widget"); + +return (
{convlist.map(conv => ( @@ -25,12 +29,39 @@ export default function ChatWidget({convlist}) { key={conv.id} id={conv.id} title={conv.title} - onOpenConv={handleOpenConv} + onOpenConv={setActiveConvId} /> ))} + +
+ +
- + {showAddUsers && activeConvId && ( + setShowAddUsers(false)} + /> + )} + + {activeConvId && ( + + )} + { + console.log("conv_Id: ",conv_id) if (!text.trim() || !conv_id) return; onSend({ @@ -55,17 +88,78 @@ function SenderBox({ conv_id, onSend, disabled }) { }; return ( -
+ ); } +function AddUsersMenu({ convId, usersById, fetchConvUsers, onValidate, onClose }) { + const [selected, setSelected] = useState(new Set()); + + //new function to fetch already present users in the conv + //let current = fetchConvUsers(convId); + + const toggleUser = (id) => { + setSelected(prev => { + const next = new Set(prev); + next.has(id) ? next.delete(id) : next.add(id); + return next; + }); + }; + + const handleValidate = () => { + if (selected.size === 0) return; + + onValidate({ + conv_id: convId, + users: [...selected], + }); + + onClose(); + }; + + + useEffect(() => { + async function load() { + console.log("fetching conv users ar :", convId); + const ids = await fetchConvUsers({ conv_id: convId }); + setSelected(new Set(ids)); + } + load(); + }, [convId, fetchConvUsers]); + + return ( +
+

Add users

+ +
    + {Object.entries(usersById).map(([id, name]) => ( +
  • + +
  • + ))} +
+ + + +
+ ); +} + + function ConvCard({id, title, onOpenConv}) { return( @@ -77,21 +171,43 @@ function ConvCard({id, title, onOpenConv}) { ) } -function MessagesBox({ messages }) { +function MessagesBox({ conv_id, fetchMessages, usersById, clientId }) { + + const [messages, setMessages] = useState([]); + + useEffect(() => { + async function load() { + const msg = await fetchMessages({ conv_id }); + setMessages(msg); + } + load(); + }, [conv_id]); + if (!messages || messages.length === 0) { return
No messages
} - console.log("FETCH RESULT =", messages); + //console.log("FETCH RESULT =", messages); return (
- {messages.map(msg => ( -
-

Sender: {msg.sender_id}

-

{msg.content}

- {msg.sent_at} -
- ))} + {messages + .slice() // don’t mutate state + .sort((a, b) => new Date(a.sent_at) - new Date(b.sent_at)) + .map(msg => { + const isMine = msg.sender_id === clientId + + return( +
+

+ Sender:{" "} + {usersById[msg.sender_id] ?? "Unknown user"} +

+

{msg.content}

+ {msg.sent_at} +
+ ) + })}
); diff --git a/src/config.js b/src/config.js index 4bcc492..adf47de 100644 --- a/src/config.js +++ b/src/config.js @@ -1 +1,2 @@ +//export const API_BASE = "http://79.137.75.155:8080"; export const API_BASE = "http://localhost:7080"; \ No newline at end of file