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() {
const { currentRoomInfo } = useRoom()
return (
<h2 className={`text-lg font-bold ${currentRoomInfo ? '' : 'text-gray-500'}`}>
{currentRoomInfo?.roomName ?? '房间'}
</h2>
<div className="bg-white p-4 mb-4">
<h2 className={`text-lg font-bold ${currentRoomInfo ? '' : 'text-gray-500'}`}>
{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';
import RoomMessageProvider from './room-message';
import SendMessageProvider from './send-message';
import UsernameCheck from './username-check';
import PageHeaderProvider from './header'
import ConfirmModalProvider from './confirm-modal';
import { RoomProvider } from './room-context'
export default function Home() {
return (
<RoomProvider>
<UsernameCheck />
<div className="bg-gray-100">
<div className="flex h-screen">
<ConfirmModalProvider />
<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">
<h2 className="text-lg font-bold mb-4">房间列表</h2>
<RoomListProvider />
</div>
<div className="w-3/4 bg-gray-100 p-4">
<div className="bg-white p-4 mb-4">
<RoomHeaderProvider />
</div>
<RoomMessageProvider />
<div className="bg-white p-4">
<SendMessageProvider />
</div>
</div>
</div>
<h2 className="text-lg font-bold mb-4">房间列表</h2>
<RoomListProvider />
</div>
<div className="w-3/4 bg-gray-100 p-4">
<RoomHeaderProvider />
<RoomMessageProvider />
<SendMessageProvider />
</div>
</div>
</div>
</RoomProvider>
);
......
......@@ -10,19 +10,41 @@ interface RoomCtxValue {
setUsername: (u: string) => void;
msgVersion: number;
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)
export function RoomProvider({ children }: { children: ReactNode }) {
/* 这段后面换成登录 */
const searchParams = useSearchParams();
const userName = searchParams.get('username')?.trim() || '';
const [currentRoomInfo, setCurrentRoomInfo] = useState<Ty.RoomPreviewInfo | null>(null)
const [username, setUsername] = useState<string>(userName);
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 triggerCreateRoom = useCallback(() => setCreateRoomVersion(v => v + 1), []);
const triggerErrorModal = useCallback((msg: string) => {
setErrorModalMsg(msg);
setShowErrorModal(true);
}, []);
return (
<RoomContext.Provider value={{
......@@ -31,7 +53,17 @@ export function RoomProvider({ children }: { children: ReactNode }) {
username,
setUsername,
msgVersion,
triggerRefresh
triggerRefresh,
showCreateModal,
setShowCreateModal,
showErrorModal,
setShowErrorModal,
errorModalMsg,
triggerErrorModal,
newRoomName,
setNewRoomName,
createRoomVersion,
triggerCreateRoom
}}>
{children}
</RoomContext.Provider>
......
......@@ -8,8 +8,149 @@ export default function RoomListProvider() {
const [roomsLoading, setRoomsLoading] = useState<boolean>(false)
const [roomsError, setRoomsError] = useState<string>('')
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
......@@ -64,14 +205,37 @@ export default function RoomListProvider() {
}
}, [msgVersion]);
useEffect(() => {
if (roomsError) {
triggerErrorModal(roomsError);
setRoomsError('');
}
}, [roomsError]);
return (
<div className="room-container">
{roomsError && <div className="text-red-500">{roomsError}</div>}
{roomsLoading && !rooms.length && <div className="text-gray-500">加载中...</div>}
<ul className="mb-2">
{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)}>
<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>
{room.lastMessage ? (
<p>{room.lastMessage.sender}: {room.lastMessage.content}</p>
......@@ -82,7 +246,16 @@ export default function RoomListProvider() {
</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>
</div>
);
}
\ No newline at end of file
......@@ -5,7 +5,11 @@ import { useRoom } from './room-context'
export default function RoomMessageProvider() {
const { currentRoomInfo, msgVersion } = useRoom()
const {
currentRoomInfo,
triggerErrorModal,
msgVersion
} = useRoom()
const [messagesLoading, setMessagesLoading] = useState<boolean>(false)
const [messagesError, setMessagesError] = useState<string>('')
......@@ -43,13 +47,13 @@ export default function RoomMessageProvider() {
});
if (!res.ok) {
throw new Error('无法获取房间列表,错误码:' + res.status);
throw new Error('无法获取消息列表,错误码:' + res.status);
}
const data: Ty.BackendResponse<Ty.RoomMessageListRes> = await res.json();
if (data.code !== 0) {
throw new Error('无法获取房间列表,错误信息:' + data.message);
throw new Error('无法获取消息列表,错误信息:' + data.message);
}
setMessages(data.data.messages);
......@@ -94,13 +98,13 @@ export default function RoomMessageProvider() {
});
if (!res.ok) {
throw new Error('无法获取房间列表,错误码:' + res.status);
throw new Error('无法更新消息列表,错误码:' + res.status);
}
const data: Ty.BackendResponse<Ty.RoomMessageGetUpdateRes> = await res.json();
if (data.code !== 0) {
throw new Error('无法获取房间列表,错误信息:' + data.message);
throw new Error('无法更新消息列表,错误信息:' + data.message);
}
setMessages((prevMessages) => [...prevMessages, ...data.data.messages]);
......@@ -142,6 +146,13 @@ export default function RoomMessageProvider() {
}
}, [messages]);
useEffect(() => {
if (messagesError) {
triggerErrorModal(messagesError);
setMessagesError('');
}
}, [messagesError]);
if(currentRoomInfo)
{
return (
......@@ -150,7 +161,6 @@ export default function RoomMessageProvider() {
className="bg-white p-4 overflow-y-auto h-3/5"
>
<div className="bg-gray-100 min-h-full">
{messagesError && <div className="text-red-500">{messagesError}</div>}
<ul className="mb-2">
{messages?.map((msg) => (
<li key={msg.messageId} className="mb-3">
......
'use client'
import { useRoom } from './room-context'
import { useState } from 'react'
import { useEffect, useState } from 'react'
import * as Ty from './interface'
export default function SendMessageProvider() {
const { username, currentRoomInfo, triggerRefresh } = useRoom()
const {
username,
currentRoomInfo,
triggerErrorModal,
triggerRefresh
} = useRoom()
const [text, setText] = useState('');
const [sendError, setSendError] = useState<string | null>(null);
......@@ -54,9 +59,15 @@ export default function SendMessageProvider() {
}
}
useEffect(() => {
if (sendError) {
triggerErrorModal(sendError);
setSendError(null);
}
}, [sendError]);
return (
<>
{sendError && <div className="text-red-500">{sendError}</div>}
<div className="bg-white p-4">
<textarea
className="w-full resize-none"
rows={4}
......@@ -75,6 +86,6 @@ export default function SendMessageProvider() {
>
发送
</button>
</>
</div>
)
}
\ No newline at end of file
......@@ -8,6 +8,7 @@ export default function Home() {
const handleSubmit = () => {
if (!username.trim()) return; // 简单校验
/* 这段后面换成登录 */
router.push(`/chat?username=${encodeURIComponent(username)}`);
};
......@@ -19,6 +20,7 @@ export default function Home() {
placeholder="用户名"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
/>
<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