diff --git a/src/apis/auth.ts b/src/apis/auth.ts index 51b5e36..ac9aca7 100644 --- a/src/apis/auth.ts +++ b/src/apis/auth.ts @@ -115,7 +115,7 @@ export const getSocialLoginUrl = (provider: SocialProvider) => export const exchangeSocialLoginCode = (code: string) => post("/api/auth/login/oauth2/exchange", { code }); -const requestLoginMethods = async (method: "GET" | "POST" | "PATCH", path = ""): Promise => { +const requestLoginMethods = async (method: "GET" | "POST" | "DELETE", path = ""): Promise => { const accessToken = getAccessToken(); if (!accessToken) throw new ApiError("로그인이 필요합니다.", 401); @@ -150,7 +150,7 @@ export const connectLoginMethod = async (provider: SocialLoginMethodProvider): P }; export const unlinkLoginMethod = (provider: SocialLoginMethodProvider) => - requestLoginMethods("PATCH", `/${provider}`); + requestLoginMethods("DELETE", `/${provider}`); export const logout = async (): Promise => { const accessToken = getAccessToken(); diff --git a/src/pages/Auth/OAuthCallbackPage.tsx b/src/pages/Auth/OAuthCallbackPage.tsx index 3c8abda..3ca8890 100644 --- a/src/pages/Auth/OAuthCallbackPage.tsx +++ b/src/pages/Auth/OAuthCallbackPage.tsx @@ -14,6 +14,11 @@ import { connectExistingPush } from "@/utils/fcm/connectExistingPush"; const pendingExchanges = new Map>(); +const oauthErrorMessages: Record = { + AUTH_409_004: + "이미 다른 Quespot 계정에 연결된 소셜 계정이에요. 해당 계정으로 로그인하거나 다른 소셜 계정을 이용해주세요.", +}; + const exchangeOnce = (code: string) => { const pending = pendingExchanges.get(code); if (pending) return pending; @@ -27,14 +32,22 @@ export default function OAuthCallbackPage() { const navigate = useNavigate(); const [searchParams] = useSearchParams(); const [error, setError] = useState(""); + const isAccountLinkConflict = searchParams.get("error") === "AUTH_409_004"; useEffect(() => { const oauthError = searchParams.get("error") || searchParams.get("message"); + const linkedProvider = searchParams.get("linkedProvider"); const code = searchParams.get("code"); if (oauthError) { cancelSocialLogin(); - setError(oauthError); + setError(oauthErrorMessages[oauthError] || oauthError); + return; + } + if (linkedProvider) { + completeSocialLogin(""); + const redirectPath = takeLoginRedirect(); + navigate(redirectPath || PATH.MY, { replace: true }); return; } if (!code) { @@ -78,7 +91,11 @@ export default function OAuthCallbackPage() {
{error ? ( <> -

로그인하지 못했어요

+

+ {isAccountLinkConflict + ? "계정을 연결하지 못했어요" + : "로그인하지 못했어요"} +

) : ( diff --git a/src/pages/My/MyPage.tsx b/src/pages/My/MyPage.tsx index fafb7b4..d987913 100644 --- a/src/pages/My/MyPage.tsx +++ b/src/pages/My/MyPage.tsx @@ -17,6 +17,7 @@ import { Link2, Pencil, Plane, + RotateCcw, Settings2, ShieldCheck, Sparkles, @@ -168,6 +169,7 @@ export default function MyPage() { ); const [draftProfileImageFile, setDraftProfileImageFile] = useState(null); + const [shouldResetProfileImage, setShouldResetProfileImage] = useState(false); const [draftInterests, setDraftInterests] = useState( profile.interests?.length ? profile.interests : DEFAULT_INTEREST_IDS, ); @@ -287,10 +289,14 @@ export default function MyPage() { try { const profileImageObjectKey = draftProfileImageFile ? await uploadFile(draftProfileImageFile, "PROFILE") - : undefined; + : shouldResetProfileImage + ? "" + : undefined; const updatedProfile = await updateBasicProfile({ nickname, - ...(profileImageObjectKey ? { profileImageObjectKey } : {}), + ...(profileImageObjectKey !== undefined + ? { profileImageObjectKey } + : {}), travelStyles: categoryIdsToTravelStyles( profile.interests?.length ? profile.interests : DEFAULT_INTEREST_IDS, ), @@ -306,6 +312,7 @@ export default function MyPage() { JSON.stringify({ ...readProfile(), ...next }), ); setDraftProfileImageFile(null); + setShouldResetProfileImage(false); setEditing(false); setStatusMessage("프로필을 수정했어요."); } catch (profileError) { @@ -323,6 +330,7 @@ export default function MyPage() { setDraftNickname(profile.nickname || "Quespot 탐험가"); setDraftProfileImage(profile.profileImageUrl || questyProfile); setDraftProfileImageFile(null); + setShouldResetProfileImage(false); setEditing(true); }; @@ -330,6 +338,7 @@ export default function MyPage() { const file = event.target.files?.[0]; if (!file) return; setDraftProfileImageFile(file); + setShouldResetProfileImage(false); const reader = new FileReader(); reader.onload = () => typeof reader.result === "string" && setDraftProfileImage(reader.result); @@ -337,6 +346,15 @@ export default function MyPage() { event.target.value = ""; }; + const handleResetProfileImage = () => { + setDraftProfileImage(questyProfile); + setDraftProfileImageFile(null); + setShouldResetProfileImage(true); + if (profileImageInputRef.current) { + profileImageInputRef.current.value = ""; + } + }; + const openTravelEditor = () => { const availableInterests = profile.interests ?? []; setDraftInterests( @@ -1049,6 +1067,11 @@ export default function MyPage() { className="h-full w-full rounded-full object-cover" src={draftProfileImage} alt="선택한 프로필 사진" + onError={() => { + if (draftProfileImage !== questyProfile) { + setDraftProfileImage(questyProfile); + } + }} /> @@ -1061,9 +1084,26 @@ export default function MyPage() { onChange={handleProfileImageChange} type="file" /> - - 사진 변경 - +

+ +