Skip to content
Snippets Groups Projects
Commit 573591e6 authored by 석찬 윤's avatar 석찬 윤
Browse files

feat: 마이페이지 meeting tab 개발 (#11)

parent 3a86c1f6
No related branches found
No related tags found
1 merge request!19[#11] 마이페이지 개발
Pipeline #10899 failed
// src/api/meeting.js
// 기본 API URL
const BASE_URL = process.env.REACT_APP_BASE_URL;
// 내가 참여하고 있는 채팅방 가져오기
export const fetchMyMeetings = async (page = 0, size = 20) => {
try {
const response = await fetch(
`${BASE_URL}/api/meeting/my?page=${page}&size=${size}`,
{
method: "GET",
credentials: "include", // 세션 기반 인증을 위해 필요
headers: {
"Content-Type": "application/json",
},
}
);
if (!response.ok) {
throw new Error(`Error: ${response.status}`);
}
const result = await response.json();
if (!result.success) {
throw new Error("Failed to fetch meetings.");
}
return result.data; // 서버에서 제공된 데이터 반환
} catch (error) {
console.error("Error fetching my meetings:", error);
throw error;
}
};
import { cn } from "../libs/index";
import { cva } from "class-variance-authority";
import React from "react";
import Label from "./Label";
// Card 스타일 변수를 정의합니다.
const cardVariants = cva("w-full rounded-xl shadow-lg p-4 overflow-hidden", {
......@@ -35,13 +36,20 @@ export default function Card({ meeting, theme = "black", onClick }) {
return (
<div className={cn(variantClass)} onClick={onClick}>
<h3 className="mb-2 text-xl font-bold">{title}</h3>
<p className="text-sm text-gray-700">{location}</p>
<p className="text-base">
<div className="flex gap-2 mb-2">
<Label size="sm" theme="indigo">
{location}
</Label>
<Label size="sm" theme="indigo">
시간: {timeIdxStart} ~ {timeIdxEnd}
</p>
<p className="text-base">마감 시간: {time_idx_deadline}</p>
</Label>
</div>
<Label size="sm" theme="indigo">
마감 시간: {time_idx_deadline}
</Label>
<div className="flex justify-between mt-2">
<span className="text-sm text-gray-600">작성자: {creatorName}</span>
<span className="text-sm text-white">작성자: {creatorName}</span>
<span className="text-sm text-right">
{type === "OPEN" ? "참여 가능" : "참여 마감"}
</span>
......
......@@ -9,7 +9,7 @@ const labelVariants = cva(
theme: {
indigo: "bg-secondary-900 text-white",
solid: "bg-primary-600 text-white",
lightsolid: "bg-primary-100 text-primary-600",
lightsolid: "bg-primary-600 text-primary-100 border border-primary-100",
graysolid: "bg-grayscale-100 text-grayscale-900",
ghost: "border border-grayscale-500 text-grayscale-900",
},
......
......@@ -10,6 +10,8 @@ import {
} from "../api/friend";
import Button from "../components/Button";
import LogoIcon from "../components/icons/LogoIcon";
import { fetchMyMeetings } from "../api/meeting";
import Card from "../components/Card";
const MyPage = () => {
const { user } = useAuthStore(); // Zustand에서 user 상태 가져오기
......@@ -22,9 +24,35 @@ const MyPage = () => {
const [hasNext, setHasNext] = useState(true); // 페이지네이션 상태
const [isLoading, setIsLoading] = useState(false);
const [meetings, setMeetings] = useState([]);
const [meetingPage, setMeetingPage] = useState(0);
const [meetingHasNext, setMeetingHasNext] = useState(true);
const [meetingIsLoading, setMeetingIsLoading] = useState(false);
// 탭 변경 함수
const switchTab = (tab) => setActiveTab(tab);
// 번개 모임 가져오기
useEffect(() => {
const fetchMeetings = async () => {
if (!meetingHasNext || meetingIsLoading) return;
try {
setMeetingIsLoading(true);
const data = await fetchMyMeetings(meetingPage, 20);
setMeetings((prev) => [...prev, ...data.content]);
setMeetingHasNext(data.meetingHasNext);
setMeetingPage((prev) => prev + 1);
} catch (error) {
console.error("Failed to fetch meetings:", error);
} finally {
setIsLoading(false);
}
};
if (activeTab === "lightning") fetchMeetings();
}, [activeTab, page, hasNext]);
// 받은 친구 요청 조회
useEffect(() => {
const fetchReceivedRequests = async () => {
......@@ -109,11 +137,6 @@ const MyPage = () => {
{/* 프로필 영역 */}
<div className="flex items-center px-6 py-4 border-b">
<div className="flex-shrink-0">
{/* <img
src={user?.profilePicture || "https://via.placeholder.com/150"}
alt="프로필 사진"
className="object-cover w-20 h-20 rounded-full"
/> */}
<LogoIcon className="w-20 h-20 rounded-full" />
</div>
<div className="flex-grow ml-6">
......@@ -145,13 +168,69 @@ const MyPage = () => {
친구
</button>
</div>
{/* 번개 모임 탭 */}
{activeTab === "lightning" && (
<div className="p-4">
{meetings.length === 0 && !isLoading && (
<p className="text-center">참여 중인 번개 모임이 없습니다.</p>
)}
<div className="grid grid-cols-1 gap-4 tablet:grid-cols-2 desktop:grid-cols-3">
{meetings.map((meeting) => (
<Card
key={meeting.id}
meeting={meeting}
theme="purple"
onClick={() => console.log("Clicked meeting:", meeting.id)}
/>
))}
</div>
{isLoading && <p className="text-center">로딩 중...</p>}
{!hasNext && meetings.length > 0 && (
<p className="text-sm text-center text-gray-500">
더 이상 불러올 번개 모임이 없습니다.
</p>
)}
</div>
)}
{/* 친구 탭 */}
{activeTab === "friends" && (
<div className="p-4 space-y-8">
{/* 친구 요청하기 */}
<div>
<h2 className="text-lg font-bold">친구 요청하기</h2>
<div className="flex items-center my-4 space-x-4">
<input
type="email"
className="flex-1 min-w-0 p-3 border rounded-full"
placeholder="친구 이메일 입력"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<Button
size="md"
theme="indigo"
className="min-w-[50px]"
onClick={handleSendRequest}
>
요청
</Button>
</div>
<div className="mt-4 space-y-4">
{sentRequests.map((request) => (
<div key={request.id} className="p-2 border rounded-lg">
<p className="text-sm">
{request.receiver.name} ({request.receiver.email})
</p>
<p className="text-xs text-gray-500">{request.status}</p>
</div>
))}
</div>
</div>
<hr />
{/* 받은 친구 요청 */}
<div>
<h2 className="text-lg font-bold">받은 친구 요청</h2>
<h2 className="mb-2 text-lg font-bold">받은 친구 요청</h2>
<div className="space-y-4">
{receivedRequests.map((request) => (
<div
......@@ -162,20 +241,20 @@ const MyPage = () => {
<h3 className="font-semibold text-md">
{request.requester.name}
</h3>
<p className="text-sm text-gray-600">
<p className="text-[10px] tablet:text-sm text-gray-600">
{request.requester.email}
</p>
</div>
<div className="space-x-2">
<div className="flex gap-2">
<Button
size="md"
size="sm"
theme="indigo"
onClick={() => handleAcceptRequest(request.id)}
>
수락
</Button>
<Button
size="md"
size="sm"
theme="pink"
onClick={() => handleRejectRequest(request.id)}
>
......@@ -187,41 +266,10 @@ const MyPage = () => {
</div>
</div>
<hr />
{/* 친구 요청하기 */}
<div>
<h2 className="text-lg font-bold">친구 요청하기</h2>
<div className="flex items-center my-4 space-x-4">
<input
type="email"
className="flex-1 min-w-0 p-3 border rounded-full"
placeholder="친구 이메일 입력"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<Button
size="md"
theme="indigo"
className="min-w-[50px]"
onClick={handleSendRequest}
>
요청
</Button>
</div>
<div className="mt-4 space-y-4">
{sentRequests.map((request) => (
<div key={request.id} className="p-2 border rounded-lg">
<p className="text-sm">
{request.receiver.name} ({request.receiver.email})
</p>
<p className="text-xs text-gray-500">{request.status}</p>
</div>
))}
</div>
</div>
<hr />
{/* 친구 목록 */}
<div>
<h2 className="text-lg font-bold">친구 목록</h2>
<h2 className="mb-2 text-lg font-bold">친구 목록</h2>
<div className="space-y-4">
{friends.map((friend) => (
<div
......@@ -232,20 +280,23 @@ const MyPage = () => {
<h3 className="font-semibold text-md">
{friend.friendInfo.name}
</h3>
<p className="text-sm text-gray-600">
<p className="text-[10px] tablet:text-sm text-gray-600">
{friend.friendInfo.email}
</p>
</div>
<button
className="px-4 py-2 text-sm text-white bg-red-500 rounded-lg hover:bg-red-600"
<Button
size="sm"
theme="black"
onClick={() => handleDeleteFriend(friend.id)}
>
삭제
</button>
</Button>
</div>
))}
{isLoading && <p>로딩 중...</p>}
{!hasNext && <p>더 이상 친구가 없습니다.</p>}
{!hasNext && (
<p className="text-center label-2">더 이상 친구가 없습니다.</p>
)}
</div>
</div>
</div>
......
......@@ -10,7 +10,7 @@ const useAuthStore = create((set) => ({
set({ loading: true });
try {
const userInfo = await getSessionInfo();
set(userInfo);
set({ user: userInfo });
} catch (error) {
console.error("Failed to fetch session info:", error);
set({ user: null });
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment