Skip to content

Commit

Permalink
Merge pull request #225 from always97/fe-user-ui
Browse files Browse the repository at this point in the history
[FE] feat#76#84#224 둜그인/νšŒμ›κ°€μž…, λ§ˆμ΄νŽ˜μ΄μ§€ ui, μ±„νŒ… 슀크둀 κ°œμ„ 
  • Loading branch information
always97 authored Nov 20, 2024
2 parents dd1cfa9 + 95d55da commit 26b57e8
Show file tree
Hide file tree
Showing 6 changed files with 348 additions and 103 deletions.
4 changes: 4 additions & 0 deletions FE/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { GameSetupPage } from './pages/GameSetupPage';
import { GamePage } from './pages/GamePage';
import { QuizSetupPage } from './pages/QuizSetupPage';
import { GameLobbyPage } from './pages/GameLobbyPage';
import { LoginPage } from './pages/LoginPage';
import { MyPage } from './pages/MyPage';

function App() {
return (
Expand All @@ -14,6 +16,8 @@ function App() {
<Route path="/game/:gameId" element={<GamePage />} />
<Route path="/game/lobby" element={<GameLobbyPage />} />
<Route path="/quiz/setup" element={<QuizSetupPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/mypage" element={<MyPage />} />
<Route path="*" element={<div>not found</div>} />
</Routes>
</Router>
Expand Down
38 changes: 24 additions & 14 deletions FE/src/components/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,38 @@ const Chat = () => {
const [isAtBottom, setIsAtBottom] = useState(true);
const [newMessage, setNewMessage] = useState(false);
const [prevMessageCount, setPrevMessageCount] = useState(messages.length);
const prevScrollTopRef = useRef(0);

const scrollToBottom = () => {
if (chatBottomRef.current) {
chatBottomRef.current.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
setNewMessage(false);
setTimeout(() => {
chatBottomRef.current?.scrollIntoView({
behavior: 'instant',
block: 'end'
});
setNewMessage(false);
}, 0);
}
};

const handleScroll = () => {
const container = chatContainerRef.current;
if (container) {
// const isBottom = container.scrollHeight - container.scrollTop === container.clientHeight;
const isBottom = prevScrollTopRef.current < container.scrollTop && isAtBottom;
prevScrollTopRef.current = container.scrollTop;
setIsAtBottom(isBottom); // 맨 μ•„λž˜μ— 있으면 true, μ•„λ‹ˆλ©΄ false
if (isBottom) {
setNewMessage(false);
}
if (!container) return;

const isBottom =
Math.abs(container.scrollHeight - container.scrollTop - container.clientHeight) < 10;

if (isBottom) {
setIsAtBottom(true);
} else {
setIsAtBottom(false);
}
};

const handleScrollToBottomClick = () => {
scrollToBottom();
setIsAtBottom(true);
};

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setInputValue(e.target.value);
};
Expand Down Expand Up @@ -78,8 +88,8 @@ const Chat = () => {
setPrevMessageCount(messages.length);
}

if (isAtBottom && chatBottomRef.current) {
scrollToBottom();
if (isAtBottom) {
requestAnimationFrame(() => scrollToBottom());
}
}, [messages, isAtBottom, prevMessageCount]);

Expand Down Expand Up @@ -127,7 +137,7 @@ const Chat = () => {
<Button
variant="contained"
className="fixed bottom-24 left-1/2 transform -translate-x-1/2 bg-blue-500 text-white p-2 rounded"
onClick={scrollToBottom}
onClick={handleScrollToBottomClick}
style={{ zIndex: 1000 }}
>
{`${messages[messages.length - 1].playerName}: ${
Expand Down
121 changes: 121 additions & 0 deletions FE/src/pages/LoginPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { HeaderBar } from '@/components/HeaderBar';
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';

export const LoginPage = () => {
const [isSignUp, setIsSignUp] = useState(false);
const navigate = useNavigate();

const handleLogin = () => {
navigate('/mypage');
};

return (
<div className="min-h-screen bg-gray-900 text-gray-300 flex flex-col">
<HeaderBar />

<div className="flex-grow flex items-center justify-center">
<div className="w-full max-w-md">
<div className="text-center mb-6">
<h6 className="text-lg font-bold">
<span
className={`px-4 ${!isSignUp ? 'text-yellow-400' : 'text-gray-400'} cursor-pointer`}
onClick={() => setIsSignUp(false)}
>
둜그인
</span>
<span
className={`px-4 ${isSignUp ? 'text-yellow-400' : 'text-gray-400'} cursor-pointer`}
onClick={() => setIsSignUp(true)}
>
νšŒμ›κ°€μž…
</span>
</h6>
<div
className="relative w-16 h-4 mx-auto bg-yellow-400 rounded-full cursor-pointer"
onClick={() => setIsSignUp(!isSignUp)}
>
<div
className={`absolute w-9 h-9 bg-blue-800 rounded-full transition-transform duration-500 ${
isSignUp ? 'translate-x-8' : ''
}`}
></div>
</div>
</div>

<div
className="bg-gray-800 rounded-md p-8 transition-all duration-500"
style={{ minHeight: '400px' }}
>
{isSignUp ? <SignUpForm /> : <LoginForm handleLogin={handleLogin} />}
</div>
</div>
</div>
</div>
);
};

type LoginFormProps = {
handleLogin: () => void;
};

const LoginForm: React.FC<LoginFormProps> = ({ handleLogin }) => (
<div>
<h4 className="mb-4 text-xl font-bold text-gray-100">둜그인</h4>
<div className="mb-4">
<input
type="email"
placeholder="이메일"
className="w-full px-4 py-3 text-sm text-gray-300 bg-gray-700 rounded-md focus:ring-2 focus:ring-yellow-400 focus:outline-none"
/>
</div>
<div className="mb-4">
<input
type="password"
placeholder="λΉ„λ°€λ²ˆν˜Έ"
className="w-full px-4 py-3 text-sm text-gray-300 bg-gray-700 rounded-md focus:ring-2 focus:ring-yellow-400 focus:outline-none"
/>
</div>
<button
className="w-full px-4 py-3 mt-4 text-sm font-semibold text-blue-900 bg-yellow-400 rounded-md hover:bg-yellow-500"
onClick={handleLogin}
>
둜그인
</button>
<p className="mt-4 text-sm">
<a href="#" className="text-yellow-400 hover:underline">
λΉ„λ°€λ²ˆν˜Έλ₯Ό μžŠμœΌμ…¨λ‚˜μš”?
</a>
</p>
</div>
);

const SignUpForm = () => (
<div>
<h4 className="mb-4 text-xl font-bold text-gray-100">νšŒμ›κ°€μž…</h4>
<div className="mb-4">
<input
type="text"
placeholder="λ‹‰λ„€μž„"
className="w-full px-4 py-3 text-sm text-gray-300 bg-gray-700 rounded-md focus:ring-2 focus:ring-yellow-400 focus:outline-none"
/>
</div>
<div className="mb-4">
<input
type="email"
placeholder="이메일"
className="w-full px-4 py-3 text-sm text-gray-300 bg-gray-700 rounded-md focus:ring-2 focus:ring-yellow-400 focus:outline-none"
/>
</div>
<div className="mb-4">
<input
type="password"
placeholder="λΉ„λ°€λ²ˆν˜Έ"
className="w-full px-4 py-3 text-sm text-gray-300 bg-gray-700 rounded-md focus:ring-2 focus:ring-yellow-400 focus:outline-none"
/>
</div>
<button className="w-full px-4 py-3 mt-4 text-sm font-semibold text-blue-900 bg-yellow-400 rounded-md hover:bg-yellow-500">
νšŒμ›κ°€μž…
</button>
</div>
);
4 changes: 3 additions & 1 deletion FE/src/pages/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export const MainPage = () => {
<Button variant="outlined" onClick={() => navigate('/quiz/setup')}>
ν€΄μ¦ˆ 생성
</Button>
<Button variant="outlined">둜그인</Button>
<Button variant="outlined" onClick={() => navigate('/login')}>
둜그인
</Button>
</div>
</div>
</div>
Expand Down
104 changes: 104 additions & 0 deletions FE/src/pages/MyPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { HeaderBar } from '@/components/HeaderBar';
import { useState } from 'react';

type QuizListProps = {
title: string;
quizzes: string[];
activeTab: string;
};

export const MyPage: React.FC = () => {
const [activeTab, setActiveTab] = useState<'myQuizzes' | 'solvedQuizzes'>('myQuizzes');

const myQuizzes = ['ν€΄μ¦ˆ 1', 'ν€΄μ¦ˆ 2', 'ν€΄μ¦ˆ 3', 'ν€΄μ¦ˆ 4']; // μ˜ˆμ‹œ 데이터
const solvedQuizzes = ['ν€΄μ¦ˆ A', 'ν€΄μ¦ˆ B', 'ν€΄μ¦ˆ C', 'ν€΄μ¦ˆ D']; // μ˜ˆμ‹œ 데이터

return (
<div className="min-h-screen bg-gray-100 text-gray-800">
<HeaderBar />

<div className="flex flex-col items-center mt-8">
<div className="w-24 h-24 rounded-full bg-gray-300 overflow-hidden mb-4">
<img
src="https://via.placeholder.com/100" // μž„μ‹œ ν”„λ‘œν•„ 이미지
alt="Profile"
className="w-full h-full object-cover"
/>
</div>
<h2 className="text-lg font-bold">λ‹‰λ„€μž„</h2>
</div>

<div className="mt-8 px-4">
<div className="flex justify-center space-x-4 border-b border-gray-300 pb-2">
<button
className={`px-4 py-2 font-medium ${
activeTab === 'myQuizzes'
? 'text-blue-500 border-b-2 border-blue-500'
: 'text-gray-500'
}`}
onClick={() => setActiveTab('myQuizzes')}
>
λ‚΄κ°€ λ§Œλ“  ν€΄μ¦ˆ
</button>
<button
className={`px-4 py-2 font-medium ${
activeTab === 'solvedQuizzes'
? 'text-blue-500 border-b-2 border-blue-500'
: 'text-gray-500'
}`}
onClick={() => setActiveTab('solvedQuizzes')}
>
λ‚΄κ°€ ν’€μ΄ν•œ ν€΄μ¦ˆ
</button>
</div>

{/* 리슀트 */}
<div className="mt-4">
{activeTab === 'myQuizzes' ? (
<QuizList title="λ‚΄κ°€ λ§Œλ“  ν€΄μ¦ˆ" quizzes={myQuizzes} activeTab={activeTab} />
) : (
<QuizList title="λ‚΄κ°€ ν’€μ΄ν•œ ν€΄μ¦ˆ" quizzes={solvedQuizzes} activeTab={activeTab} />
)}
</div>
</div>
</div>
);
};

// ν€΄μ¦ˆ 리슀트 μ»΄ν¬λ„ŒνŠΈ
const QuizList: React.FC<QuizListProps> = ({ title, quizzes, activeTab }) => {
const handleEdit = (index: number) => {
console.log(index + 'μˆ˜μ • 클릭');
};
const handleDelete = (index: number) => {
console.log(index + 'μ‚­μ œ 클릭');
};
return (
<div className="max-w-lg mx-auto p-4 bg-white shadow-md rounded-md overflow-y-auto max-h-90">
<h3 className="text-lg font-bold mb-4">{title}</h3>
<ul className="space-y-2">
{quizzes.map((quiz, index) => (
<li key={index} className="p-2 border-b flex justify-between items-center">
<span>{quiz}</span>
<div className="flex gap-2">
{activeTab === 'myQuizzes' && (
<button
onClick={() => handleEdit(index)}
className="px-2 py-1 bg-blue-500 text-white rounded hover:bg-blue-600"
>
μˆ˜μ •
</button>
)}
<button
onClick={() => handleDelete(index)}
className="px-2 py-1 bg-red-500 text-white rounded hover:bg-red-600"
>
μ‚­μ œ
</button>
</div>
</li>
))}
</ul>
</div>
);
};
Loading

0 comments on commit 26b57e8

Please sign in to comment.