Commit 29493cc4 authored by 何 广一's avatar 何 广一
Browse files

the real last time of frontend qwq

parent 353e4327
'use client'
import { useState, useEffect } from 'react'
import * as Ty from './interface'
import { useRoom } from './room-context'
export default function ConfirmModalProvider() {
const {
showCreateModal,
setShowCreateModal,
newRoomName,
setNewRoomName,
showErrorModal,
setShowErrorModal,
errorModalMsg,
triggerCreateRoom } = useRoom();
return (
<>
{showCreateModal &&
<div
className="fixed inset-0 bg-black/40 flex items-center justify-center z-50"
onClick={() => setShowCreateModal(false)}
>
<div
className="bg-white p-8 rounded shadow-lg min-w-[300px]"
onClick={(e) => e.stopPropagation()}
>
<h2 className="text-lg mb-2">新建房间</h2>
<input
className="border px-3 py-2 mb-3 w-full"
placeholder="房间名称"
value={newRoomName}
onChange={(e) => setNewRoomName(e.target.value)}
/>
<div className="flex justify-end space-x-2">
<button
className="bg-gray-300 px-4 py-2 rounded"
onClick={() => setShowCreateModal(false)}
>
取消
</button>
<button
className="bg-blue-500 text-white px-4 py-2 rounded"
onClick={triggerCreateRoom}
>
确定
</button>
</div>
</div>
</div>
}
{showErrorModal &&
<div
className="fixed inset-0 bg-black/40 flex items-center justify-center z-50 "
onClick={() => setShowErrorModal(false)}
>
<div
className="bg-white p-8 rounded shadow-lg min-w-[300px]"
onClick={(e) => e.stopPropagation()}
>
<h2 className="text-lg mb-2">错误</h2>
<p className='p-1'>{errorModalMsg}</p>
<div className="flex justify-end space-x-2">
<button
className="bg-gray-300 px-4 py-2 mt-2 rounded hover:bg-gray-400 active:bg-gray-500"
onClick={() => setShowErrorModal(false)}
>
关闭
</button>
</div>
</div>
</div>
}
</>
);
}
\ No newline at end of file
...@@ -8,8 +8,10 @@ export default function RoomHeaderProvider() { ...@@ -8,8 +8,10 @@ export default function RoomHeaderProvider() {
const { currentRoomInfo } = useRoom() const { currentRoomInfo } = useRoom()
return ( return (
<h2 className={`text-lg font-bold ${currentRoomInfo ? '' : 'text-gray-500'}`}> <div className="bg-white p-4 mb-4">
{currentRoomInfo?.roomName ?? '房间'} <h2 className={`text-lg font-bold ${currentRoomInfo ? '' : 'text-gray-500'}`}>
</h2> {currentRoomInfo?.roomName ?? '房间'}
</h2>
</div>
); );
}; };
\ No newline at end of file
'use client'
import { useRoom } from './room-context'
import { useRouter } from 'next/navigation';
export default function PageHeaderProvider() {
const { username } = useRoom();
const router = useRouter();
const logout = () => {
/* 这段后面换成登录 */
router.push('/');
};
return (
<header className="h-14 bg-white shadow flex items-center px-4 shrink-0">
<h1 className="text-lg font-bold text-blue-600">XLAB聊天室</h1>
<div className="ml-auto flex items-center space-x-4">
<span className="text-sm text-gray-600">欢迎你,{username}</span>
<button onClick={logout} className="text-sm text-gray-600 hover:underline">
退出
</button>
</div>
</header>
);
}
\ No newline at end of file
...@@ -4,33 +4,29 @@ import RoomHeaderProvider from './current-room'; ...@@ -4,33 +4,29 @@ import RoomHeaderProvider from './current-room';
import RoomMessageProvider from './room-message'; import RoomMessageProvider from './room-message';
import SendMessageProvider from './send-message'; import SendMessageProvider from './send-message';
import UsernameCheck from './username-check'; import UsernameCheck from './username-check';
import PageHeaderProvider from './header'
import ConfirmModalProvider from './confirm-modal';
import { RoomProvider } from './room-context' import { RoomProvider } from './room-context'
export default function Home() { export default function Home() {
return ( return (
<RoomProvider> <RoomProvider>
<UsernameCheck /> <UsernameCheck />
<div className="bg-gray-100"> <ConfirmModalProvider />
<div className="flex h-screen"> <div className="bg-gray-100 flex-col flex h-screen">
<PageHeaderProvider />
<div className="flex flex-1 overflow-hidden">
<div className="w-1/4 bg-white p-4 overflow-y-auto"> <div className="w-1/4 bg-white p-4 overflow-y-auto">
<h2 className="text-lg font-bold mb-4">房间列表</h2> <h2 className="text-lg font-bold mb-4">房间列表</h2>
<RoomListProvider /> <RoomListProvider />
</div> </div>
<div className="w-3/4 bg-gray-100 p-4">
<div className="w-3/4 bg-gray-100 p-4"> <RoomHeaderProvider />
<div className="bg-white p-4 mb-4"> <RoomMessageProvider />
<RoomHeaderProvider /> <SendMessageProvider />
</div> </div>
</div>
<RoomMessageProvider />
<div className="bg-white p-4">
<SendMessageProvider />
</div>
</div>
</div>
</div> </div>
</RoomProvider> </RoomProvider>
); );
......
...@@ -10,19 +10,41 @@ interface RoomCtxValue { ...@@ -10,19 +10,41 @@ interface RoomCtxValue {
setUsername: (u: string) => void; setUsername: (u: string) => void;
msgVersion: number; msgVersion: number;
triggerRefresh: () => void; triggerRefresh: () => void;
showCreateModal: boolean;
setShowCreateModal: (show: boolean) => void;
showErrorModal: boolean;
setShowErrorModal: (error: boolean) => void;
errorModalMsg: string;
triggerErrorModal: (msg: string) => void;
newRoomName: string;
setNewRoomName: (name: string) => void;
createRoomVersion: number;
triggerCreateRoom: () => void;
} }
const RoomContext = createContext<RoomCtxValue>({} as RoomCtxValue) const RoomContext = createContext<RoomCtxValue>({} as RoomCtxValue)
export function RoomProvider({ children }: { children: ReactNode }) { export function RoomProvider({ children }: { children: ReactNode }) {
/* 这段后面换成登录 */
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const userName = searchParams.get('username')?.trim() || ''; const userName = searchParams.get('username')?.trim() || '';
const [currentRoomInfo, setCurrentRoomInfo] = useState<Ty.RoomPreviewInfo | null>(null) const [currentRoomInfo, setCurrentRoomInfo] = useState<Ty.RoomPreviewInfo | null>(null)
const [username, setUsername] = useState<string>(userName); const [username, setUsername] = useState<string>(userName);
const [msgVersion, setMsgVersion] = useState(0); const [msgVersion, setMsgVersion] = useState(0);
const [showCreateModal, setShowCreateModal] = useState(false);
const [newRoomName, setNewRoomName] = useState('');
const [createRoomVersion, setCreateRoomVersion] = useState(0);
const [showErrorModal, setShowErrorModal] = useState(false);
const [errorModalMsg, setErrorModalMsg] = useState('');
const triggerRefresh = useCallback(() => setMsgVersion(v => v + 1), []); const triggerRefresh = useCallback(() => setMsgVersion(v => v + 1), []);
const triggerCreateRoom = useCallback(() => setCreateRoomVersion(v => v + 1), []);
const triggerErrorModal = useCallback((msg: string) => {
setErrorModalMsg(msg);
setShowErrorModal(true);
}, []);
return ( return (
<RoomContext.Provider value={{ <RoomContext.Provider value={{
...@@ -31,7 +53,17 @@ export function RoomProvider({ children }: { children: ReactNode }) { ...@@ -31,7 +53,17 @@ export function RoomProvider({ children }: { children: ReactNode }) {
username, username,
setUsername, setUsername,
msgVersion, msgVersion,
triggerRefresh triggerRefresh,
showCreateModal,
setShowCreateModal,
showErrorModal,
setShowErrorModal,
errorModalMsg,
triggerErrorModal,
newRoomName,
setNewRoomName,
createRoomVersion,
triggerCreateRoom
}}> }}>
{children} {children}
</RoomContext.Provider> </RoomContext.Provider>
......
...@@ -8,8 +8,149 @@ export default function RoomListProvider() { ...@@ -8,8 +8,149 @@ export default function RoomListProvider() {
const [roomsLoading, setRoomsLoading] = useState<boolean>(false) const [roomsLoading, setRoomsLoading] = useState<boolean>(false)
const [roomsError, setRoomsError] = useState<string>('') const [roomsError, setRoomsError] = useState<string>('')
const [rooms, setRooms] = useState<Ty.RoomPreviewInfo[]>([]) const [rooms, setRooms] = useState<Ty.RoomPreviewInfo[]>([])
const { setCurrentRoomInfo, msgVersion } = useRoom() const {
username,
currentRoomInfo,
setCurrentRoomInfo,
msgVersion,
triggerRefresh,
setShowCreateModal,
triggerErrorModal,
createRoomVersion,
newRoomName,
setNewRoomName
} = useRoom()
/*
- url: /api/room/add
- method: POST
- argument:
interface RoomAddArgs {
user: string;
roomName: string;
}
- response:
interface RoomAddResult {
roomId: number;
}
*/
const createRoom = async (roomName : string) : Promise<number> => {
const args: Ty.RoomAddArgs = {
user: username,
roomName
};
setRoomsError('');
try{
const res = await fetch(Ty.urlPrefix + '/api/room/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(args)
});
if (!res.ok) {
throw new Error('创建房间失败,错误码:' + res.status);
}
const data: Ty.BackendResponse<Ty.RoomAddResult> = await res.json();
if (data.code !== 0) {
throw new Error('创建房间失败,错误信息:' + data.message);
}
return data.data.roomId;
} catch (error) {
console.error('创建房间失败:', error);
setRoomsError(error.message);
}
return -1;
}
const handleCreate = async () => {
if (!newRoomName.trim()) return;
const roomId = await createRoom(newRoomName.trim());
if (roomId === -1) return;
setNewRoomName('');
setShowCreateModal(false);
triggerRefresh();
setCurrentRoomInfo({roomId, roomName: newRoomName.trim(), lastMessage: null});
};
useEffect(() => {
handleCreate();
}, [createRoomVersion]);
/*
- url: /api/room/delete
- method: POST
- argument:
interface RoomDeleteArgs {
user: string;
roomId: number;
}
- response: null (只要code为0即为成功)
*/
const deleteRoom = async (roomId: number) => {
const args: Ty.RoomDeleteArgs = {
user: username,
roomId
};
setRoomsError('');
try {
const res = await fetch(Ty.urlPrefix + '/api/room/delete', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(args)
});
if (!res.ok) {
throw new Error('删除房间失败,错误码:' + res.status);
}
const data: Ty.BackendResponse<null> = await res.json();
/*
Error : You are not the creator. deletion failed.
{
"code": 2,
"message": "auth error: invalid identity",
"data": null
}
*/
if (data.code === 2) {
triggerErrorModal('你不是房间创建者,无法删除房间。');
return false;
}
else if (data.code !== 0) {
throw new Error('删除房间失败,错误信息:' + data.message);
}
return true;
} catch (error) {
console.error('删除房间失败:', error);
setRoomsError(error.message);
}
return false;
}
const handleDeleteRoom = async (roomId: number) => {
const success = await deleteRoom(roomId);
if (success) {
if (currentRoomInfo?.roomId === roomId) {
setCurrentRoomInfo(null);
}
triggerRefresh();
}
}
/* /*
- url: /api/room/list - url: /api/room/list
...@@ -64,14 +205,37 @@ export default function RoomListProvider() { ...@@ -64,14 +205,37 @@ export default function RoomListProvider() {
} }
}, [msgVersion]); }, [msgVersion]);
useEffect(() => {
if (roomsError) {
triggerErrorModal(roomsError);
setRoomsError('');
}
}, [roomsError]);
return ( return (
<div className="room-container"> <div className="room-container">
{roomsError && <div className="text-red-500">{roomsError}</div>}
{roomsLoading && !rooms.length && <div className="text-gray-500">加载中...</div>} {roomsLoading && !rooms.length && <div className="text-gray-500">加载中...</div>}
<ul className="mb-2"> <ul className="mb-2">
{rooms && rooms.map((room) => ( {rooms && rooms.map((room) => (
<li key={room.roomId} > <li key={room.roomId} className="relative">
<div className="bg-gray-200 p-4 mb-2 border-b border-gray-300 rounded w-full" onClick={() => handleRoomClick(room)}> <div className="bg-gray-200 p-4 mb-2 border-b border-gray-300 rounded w-full" onClick={() => handleRoomClick(room)}>
<button
className="
absolute top-1 right-1
w-6 h-6
bg-red-500 hover:bg-red-600 active:bg-red-700
text-white text-sm font-bold
rounded
flex items-center justify-center
z-10
"
onClick={(e) => {
e.stopPropagation(); // 阻止冒泡,避免触发整行点击
handleDeleteRoom(room.roomId);
}}
>
</button>
<h3>{room.roomName} (ID: {room.roomId})</h3> <h3>{room.roomName} (ID: {room.roomId})</h3>
{room.lastMessage ? ( {room.lastMessage ? (
<p>{room.lastMessage.sender}: {room.lastMessage.content}</p> <p>{room.lastMessage.sender}: {room.lastMessage.content}</p>
...@@ -82,7 +246,16 @@ export default function RoomListProvider() { ...@@ -82,7 +246,16 @@ export default function RoomListProvider() {
</li> </li>
)) ))
} }
<li>
<button
className="w-full bg-blue-500 text-white rounded p-4 text-2xl hover:bg-blue-600 active:bg-blue-700"
onClick={() => setShowCreateModal(true)}
>
+
</button>
</li>
</ul> </ul>
</div> </div>
); );
} }
\ No newline at end of file
...@@ -5,7 +5,11 @@ import { useRoom } from './room-context' ...@@ -5,7 +5,11 @@ import { useRoom } from './room-context'
export default function RoomMessageProvider() { export default function RoomMessageProvider() {
const { currentRoomInfo, msgVersion } = useRoom() const {
currentRoomInfo,
triggerErrorModal,
msgVersion
} = useRoom()
const [messagesLoading, setMessagesLoading] = useState<boolean>(false) const [messagesLoading, setMessagesLoading] = useState<boolean>(false)
const [messagesError, setMessagesError] = useState<string>('') const [messagesError, setMessagesError] = useState<string>('')
...@@ -43,13 +47,13 @@ export default function RoomMessageProvider() { ...@@ -43,13 +47,13 @@ export default function RoomMessageProvider() {
}); });
if (!res.ok) { if (!res.ok) {
throw new Error('无法获取房间列表,错误码:' + res.status); throw new Error('无法获取消息列表,错误码:' + res.status);
} }
const data: Ty.BackendResponse<Ty.RoomMessageListRes> = await res.json(); const data: Ty.BackendResponse<Ty.RoomMessageListRes> = await res.json();
if (data.code !== 0) { if (data.code !== 0) {
throw new Error('无法获取房间列表,错误信息:' + data.message); throw new Error('无法获取消息列表,错误信息:' + data.message);
} }
setMessages(data.data.messages); setMessages(data.data.messages);
...@@ -94,13 +98,13 @@ export default function RoomMessageProvider() { ...@@ -94,13 +98,13 @@ export default function RoomMessageProvider() {
}); });
if (!res.ok) { if (!res.ok) {
throw new Error('无法获取房间列表,错误码:' + res.status); throw new Error('无法更新消息列表,错误码:' + res.status);
} }
const data: Ty.BackendResponse<Ty.RoomMessageGetUpdateRes> = await res.json(); const data: Ty.BackendResponse<Ty.RoomMessageGetUpdateRes> = await res.json();
if (data.code !== 0) { if (data.code !== 0) {
throw new Error('无法获取房间列表,错误信息:' + data.message); throw new Error('无法更新消息列表,错误信息:' + data.message);
} }
setMessages((prevMessages) => [...prevMessages, ...data.data.messages]); setMessages((prevMessages) => [...prevMessages, ...data.data.messages]);
...@@ -142,6 +146,13 @@ export default function RoomMessageProvider() { ...@@ -142,6 +146,13 @@ export default function RoomMessageProvider() {
} }
}, [messages]); }, [messages]);
useEffect(() => {
if (messagesError) {
triggerErrorModal(messagesError);
setMessagesError('');
}
}, [messagesError]);
if(currentRoomInfo) if(currentRoomInfo)
{ {
return ( return (
...@@ -150,7 +161,6 @@ export default function RoomMessageProvider() { ...@@ -150,7 +161,6 @@ export default function RoomMessageProvider() {
className="bg-white p-4 overflow-y-auto h-3/5" className="bg-white p-4 overflow-y-auto h-3/5"
> >
<div className="bg-gray-100 min-h-full"> <div className="bg-gray-100 min-h-full">
{messagesError && <div className="text-red-500">{messagesError}</div>}
<ul className="mb-2"> <ul className="mb-2">
{messages?.map((msg) => ( {messages?.map((msg) => (
<li key={msg.messageId} className="mb-3"> <li key={msg.messageId} className="mb-3">
......
'use client' 'use client'
import { useRoom } from './room-context' import { useRoom } from './room-context'
import { useState } from 'react' import { useEffect, useState } from 'react'
import * as Ty from './interface' import * as Ty from './interface'
export default function SendMessageProvider() { export default function SendMessageProvider() {
const { username, currentRoomInfo, triggerRefresh } = useRoom() const {
username,
currentRoomInfo,
triggerErrorModal,
triggerRefresh
} = useRoom()
const [text, setText] = useState(''); const [text, setText] = useState('');
const [sendError, setSendError] = useState<string | null>(null); const [sendError, setSendError] = useState<string | null>(null);
...@@ -54,9 +59,15 @@ export default function SendMessageProvider() { ...@@ -54,9 +59,15 @@ export default function SendMessageProvider() {
} }
} }
useEffect(() => {
if (sendError) {
triggerErrorModal(sendError);
setSendError(null);
}
}, [sendError]);
return ( return (
<> <div className="bg-white p-4">
{sendError && <div className="text-red-500">{sendError}</div>}
<textarea <textarea
className="w-full resize-none" className="w-full resize-none"
rows={4} rows={4}
...@@ -75,6 +86,6 @@ export default function SendMessageProvider() { ...@@ -75,6 +86,6 @@ export default function SendMessageProvider() {
> >
发送 发送
</button> </button>
</> </div>
) )
} }
\ No newline at end of file
...@@ -8,6 +8,7 @@ export default function Home() { ...@@ -8,6 +8,7 @@ export default function Home() {
const handleSubmit = () => { const handleSubmit = () => {
if (!username.trim()) return; // 简单校验 if (!username.trim()) return; // 简单校验
/* 这段后面换成登录 */
router.push(`/chat?username=${encodeURIComponent(username)}`); router.push(`/chat?username=${encodeURIComponent(username)}`);
}; };
...@@ -19,6 +20,7 @@ export default function Home() { ...@@ -19,6 +20,7 @@ export default function Home() {
placeholder="用户名" placeholder="用户名"
value={username} value={username}
onChange={(e) => setUsername(e.target.value)} onChange={(e) => setUsername(e.target.value)}
required required
/> />
<button <button
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment