-
-
-

+ {/* 1) BackHeader: 절대 위치로 화면 왼쪽 상단에 고정 */}
+
+
+
+
+ {/* 2) 메인 컨테이너: 화면 중앙에 너비 제한 없이 채움 */}
+
+ {/* 3) 카드 배경 없이 바로 폼 콘텐츠 */}
+
+ {/* ▷ 이미지 업로더 */}
+
+
+
+
+ {/* ▷ 입력 폼 */}
+
+
-
-
+
+
+
-
diff --git a/src/pages/Profile/components/BackHeader.tsx b/src/pages/Profile/components/BackHeader.tsx
new file mode 100644
index 0000000..e31506d
--- /dev/null
+++ b/src/pages/Profile/components/BackHeader.tsx
@@ -0,0 +1,25 @@
+// src/pages/Profile/components/BackHeader.tsx
+
+import { ChevronLeft } from "lucide-react";
+import { useNavigate } from "react-router-dom";
+
+interface BackHeaderProps {
+ title: string;
+}
+
+export default function BackHeader({ title }: BackHeaderProps) {
+ const navigate = useNavigate();
+ return (
+ // ml-4 → left margin: 16px
+ // h-14 → height: 56px (= 14 * 4px)
+
+
+
+ );
+}
diff --git a/src/pages/Profile/components/ImageUploader.tsx b/src/pages/Profile/components/ImageUploader.tsx
new file mode 100644
index 0000000..c062470
--- /dev/null
+++ b/src/pages/Profile/components/ImageUploader.tsx
@@ -0,0 +1,52 @@
+// src/pages/Profile/components/ImageUploader.tsx
+
+interface ImageUploaderProps {
+ previewUrl: string | null;
+ onFileChange: (file: File) => void;
+ onUploadClick: () => void;
+}
+
+export default function ImageUploader({
+ previewUrl,
+ onFileChange,
+ onUploadClick,
+}: ImageUploaderProps) {
+ return (
+
+ {/* 원형 프로필 미리보기 */}
+
+

{
+ e.currentTarget.src = "/images/user_avatar.png";
+ }}
+ />
+
+
+ {/* 이미지 선택 버튼 */}
+
+
+ {/* 이미지 업로드 버튼 */}
+
+
+ );
+}
diff --git a/src/pages/Profile/components/IntroductionField.tsx b/src/pages/Profile/components/IntroductionField.tsx
new file mode 100644
index 0000000..3388506
--- /dev/null
+++ b/src/pages/Profile/components/IntroductionField.tsx
@@ -0,0 +1,29 @@
+// src/pages/Profile/components/IntroductionField.tsx
+interface IntroductionFieldProps {
+ introduction: string;
+ onChange: (value: string) => void;
+}
+
+export default function IntroductionField({
+ introduction,
+ onChange,
+}: IntroductionFieldProps) {
+ return (
+
+
+
+ );
+}
diff --git a/src/pages/Profile/components/NicknameField.tsx b/src/pages/Profile/components/NicknameField.tsx
new file mode 100644
index 0000000..c014742
--- /dev/null
+++ b/src/pages/Profile/components/NicknameField.tsx
@@ -0,0 +1,40 @@
+// src/pages/Profile/components/NicknameField.tsx
+
+interface NicknameFieldProps {
+ nickname: string;
+ onChange: (value: string) => void;
+ onCheck: () => void;
+}
+
+export default function NicknameField({
+ nickname,
+ onChange,
+ onCheck,
+}: NicknameFieldProps) {
+ return (
+
+
+
+ onChange(e.target.value)}
+ className="flex-1 rounded-l-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 lg:px-4 lg:py-3 lg:text-base"
+ />
+
+
+
+ );
+}
diff --git a/src/pages/Profile/components/Profile.tsx b/src/pages/Profile/components/Profile.tsx
deleted file mode 100644
index 304c830..0000000
--- a/src/pages/Profile/components/Profile.tsx
+++ /dev/null
@@ -1,249 +0,0 @@
-import { useEffect, useState } from "react";
-import { useNavigate } from "react-router-dom";
-import {
- getTeamLogoByIdx,
- getTeamNameByIdx,
-} from "../../../hooks/TeamNameChanger"; // 실제 경로에 맞게 수정!
-
-interface ProfileData {
- userIdx: number;
- nickname: string;
- cheeringTeamId: number;
- bio: string;
- profileImageUrl: string;
- userTemp: number;
-}
-
-interface MyMatchingPostDto {
- matchingPostIdx: number;
- userIdx: number;
- gameIdx: number;
- stadiumIdx: number;
- teamIdx: number;
- title: string;
- context: string;
- haveTicket: boolean;
- createdAt: string;
- isMatched: boolean;
-}
-
-export default function Profile() {
- const navigate = useNavigate();
-
- // 프로필 정보 상태
- const [profile, setProfile] = useState
(null);
- // 내가 쓴 매칭 글 상태
- const [myPosts, setMyPosts] = useState([]);
- const [isLoadingPosts, setIsLoadingPosts] = useState(false);
- const [postsError, setPostsError] = useState(null);
-
- useEffect(() => {
- (async () => {
- try {
- const token = localStorage.getItem("jwtToken");
- if (!token) throw new Error("토큰이 없습니다.");
-
- // 1) 프로필 정보 조회
- const profileRes = await fetch(
- `${import.meta.env.VITE_API_URL}/mypage/myTemp`,
- {
- method: "GET",
- credentials: "include",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${token}`,
- },
- }
- );
- if (!profileRes.ok)
- throw new Error(`프로필 조회 실패: ${profileRes.status}`);
- const profileData: ProfileData = await profileRes.json();
- setProfile(profileData);
-
- // 2) 내가 쓴 매칭 글 목록 조회 (/mypage/myPost 호출)
- setIsLoadingPosts(true);
- setPostsError(null);
-
- const postsRes = await fetch(
- `${import.meta.env.VITE_API_URL}/mypage/myPost`,
- {
- method: "GET",
- credentials: "include",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${token}`,
- },
- }
- );
- if (!postsRes.ok)
- throw new Error(`내 글 조회 실패: ${postsRes.status}`);
- const postsData: MyMatchingPostDto[] = await postsRes.json();
- setMyPosts(postsData);
- } catch (err: any) {
- console.error("프로필 또는 매칭 글 불러오기 실패:", err);
- setPostsError("내가 쓴 매칭 글을 불러오는 중 오류가 발생했습니다.");
- } finally {
- setIsLoadingPosts(false);
- }
- })();
- }, []);
-
- // 팀명/로고 파생 값
- const cheeringTeamName =
- profile?.cheeringTeamId !== undefined && profile?.cheeringTeamId !== null
- ? getTeamNameByIdx(profile.cheeringTeamId)
- : "로딩 중";
- const cheeringTeamLogo =
- profile?.cheeringTeamId !== undefined && profile?.cheeringTeamId !== null
- ? getTeamLogoByIdx(profile.cheeringTeamId)
- : "/images/default_team_emb.png";
-
- return (
- <>
- {/* 헤더 배너 */}
-
-
-
- {/* 프로필 + 소개 */}
-
-

-
-
-
- {profile?.nickname || "로딩 중..."}
-
-
- {cheeringTeamName}
-
-
-
{profile?.bio || "자기소개가 없습니다."}
- {/* 수정/설정 버튼 */}
-
-
-
-
-
-
- {/* 팀 엠블럼 */}
-
-

{
- e.currentTarget.src = "/images/default_team_emb.png";
- }}
- />
-
-
-
-
- {/* 직관 온도 */}
-
-
- 직관 온도
-
- {profile?.userTemp !== undefined && profile?.userTemp !== null
- ? `${profile.userTemp.toFixed(1)}℃`
- : "–"}
-
-
-
-
-
- {/* 작성한 매칭 글 */}
-
-
- 작성한 매칭 글
-
- {isLoadingPosts && (
- 불러오는 중...
- )}
- {postsError && (
- {postsError}
- )}
- {!isLoadingPosts && !postsError && myPosts.length === 0 && (
-
- 작성한 매칭 글이 없습니다.
-
- )}
- {!isLoadingPosts && !postsError && myPosts.length > 0 && (
-
- {myPosts.map((post) => (
-
- navigate(`/matching/articles/${post.matchingPostIdx}`)
- }
- >
-
-
-
-
- {profile?.nickname || ""} ·{" "}
- {new Date(post.createdAt).toLocaleDateString("ko-KR")}
-
-
-
- {post.title}
-
-
- {post.context.length > 30
- ? post.context.slice(0, 30) + "…"
- : post.context}
-
-
-
-
-
- {cheeringTeamName}
-
-
- {post.haveTicket ? "티켓 있음" : "티켓 없음"}
-
-
-
- {new Date(post.createdAt).toLocaleDateString("ko-KR")}
-
-
-
- ))}
-
- )}
-
- >
- );
-}
diff --git a/src/pages/Profile/components/SubmitButton.tsx b/src/pages/Profile/components/SubmitButton.tsx
new file mode 100644
index 0000000..0c3fdbd
--- /dev/null
+++ b/src/pages/Profile/components/SubmitButton.tsx
@@ -0,0 +1,24 @@
+// src/pages/Profile/components/SubmitButton.tsx
+
+
+interface SubmitButtonProps {
+ disabled?: boolean;
+}
+
+export default function SubmitButton({ disabled = false }: SubmitButtonProps) {
+ return (
+
+
+
+ );
+}
diff --git a/src/pages/Profile/components/Teamselect.tsx b/src/pages/Profile/components/Teamselect.tsx
new file mode 100644
index 0000000..76fc902
--- /dev/null
+++ b/src/pages/Profile/components/Teamselect.tsx
@@ -0,0 +1,36 @@
+// src/pages/Profile/components/TeamSelect.tsx
+
+interface TeamSelectProps {
+ team: string;
+ teams: string[];
+ onChange: (value: string) => void;
+}
+
+export default function TeamSelect({
+ team,
+ teams,
+ onChange,
+}: TeamSelectProps) {
+ return (
+
+
+
+
+ );
+}
diff --git a/src/pages/Profile/components/WithdrawModal.tsx b/src/pages/Profile/components/WithdrawModal.tsx
new file mode 100644
index 0000000..aef1f60
--- /dev/null
+++ b/src/pages/Profile/components/WithdrawModal.tsx
@@ -0,0 +1,47 @@
+// src/pages/Profile/components/WithdrawModal.tsx
+import { X } from "lucide-react";
+
+interface WithdrawModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ onConfirm: () => void;
+}
+
+export default function WithdrawModal({
+ isOpen,
+ onClose,
+ onConfirm,
+}: WithdrawModalProps) {
+ if (!isOpen) return null;
+
+ return (
+
+
+
+
회원 탈퇴
+
+ 탈퇴 시, 모든 데이터가 영구적으로 삭제됩니다.
+
+
+
+
+
+
+
+ );
+}