Skip to content
Merged

Main #37

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/apis/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export const getSocialLoginUrl = (provider: SocialProvider) =>
export const exchangeSocialLoginCode = (code: string) =>
post<LoginResult>("/api/auth/login/oauth2/exchange", { code });

const requestLoginMethods = async <T,>(method: "GET" | "POST" | "PATCH", path = ""): Promise<T> => {
const requestLoginMethods = async <T,>(method: "GET" | "POST" | "DELETE", path = ""): Promise<T> => {
const accessToken = getAccessToken();
if (!accessToken) throw new ApiError("๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.", 401);

Expand Down Expand Up @@ -150,7 +150,7 @@ export const connectLoginMethod = async (provider: SocialLoginMethodProvider): P
};

export const unlinkLoginMethod = (provider: SocialLoginMethodProvider) =>
requestLoginMethods<null>("PATCH", `/${provider}`);
requestLoginMethods<null>("DELETE", `/${provider}`);

export const logout = async (): Promise<void> => {
const accessToken = getAccessToken();
Expand Down
31 changes: 27 additions & 4 deletions src/pages/Auth/OAuthCallbackPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import { connectExistingPush } from "@/utils/fcm/connectExistingPush";

const pendingExchanges = new Map<string, Promise<LoginResult>>();

const oauthErrorMessages: Record<string, string> = {
AUTH_409_004:
"์ด๋ฏธ ๋‹ค๋ฅธ Quespot ๊ณ„์ •์— ์—ฐ๊ฒฐ๋œ ์†Œ์…œ ๊ณ„์ •์ด์—์š”. ํ•ด๋‹น ๊ณ„์ •์œผ๋กœ ๋กœ๊ทธ์ธํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์†Œ์…œ ๊ณ„์ •์„ ์ด์šฉํ•ด์ฃผ์„ธ์š”.",
};

const exchangeOnce = (code: string) => {
const pending = pendingExchanges.get(code);
if (pending) return pending;
Expand All @@ -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) {
Expand Down Expand Up @@ -78,7 +91,11 @@ export default function OAuthCallbackPage() {
<section className="w-full max-w-[320px] rounded-[24px] bg-white px-6 py-10 shadow-[0_8px_30px_rgba(62,102,142,0.08)]">
{error ? (
<>
<h1 className="type-label1">๋กœ๊ทธ์ธํ•˜์ง€ ๋ชปํ–ˆ์–ด์š”</h1>
<h1 className="type-label1">
{isAccountLinkConflict
? "๊ณ„์ •์„ ์—ฐ๊ฒฐํ•˜์ง€ ๋ชปํ–ˆ์–ด์š”"
: "๋กœ๊ทธ์ธํ•˜์ง€ ๋ชปํ–ˆ์–ด์š”"}
</h1>
<p
className="type-caption2 mb-6 mt-3 break-keep text-[#e46f6f]"
role="alert"
Expand All @@ -87,10 +104,16 @@ export default function OAuthCallbackPage() {
</p>
<button
className="type-body6 h-11 w-full rounded-xl bg-[#5bb5f8] text-white"
onClick={() => navigate(PATH.LOGIN, { replace: true })}
onClick={() =>
navigate(isAccountLinkConflict ? PATH.MY : PATH.LOGIN, {
replace: true,
})
}
type="button"
>
๋กœ๊ทธ์ธ์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ
{isAccountLinkConflict
? "๋งˆ์ดํŽ˜์ด์ง€๋กœ ๋Œ์•„๊ฐ€๊ธฐ"
: "๋กœ๊ทธ์ธ์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ"}
</button>
</>
) : (
Expand Down
50 changes: 45 additions & 5 deletions src/pages/My/MyPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
Link2,
Pencil,
Plane,
RotateCcw,
Settings2,
ShieldCheck,
Sparkles,
Expand Down Expand Up @@ -168,6 +169,7 @@ export default function MyPage() {
);
const [draftProfileImageFile, setDraftProfileImageFile] =
useState<File | null>(null);
const [shouldResetProfileImage, setShouldResetProfileImage] = useState(false);
const [draftInterests, setDraftInterests] = useState<string[]>(
profile.interests?.length ? profile.interests : DEFAULT_INTEREST_IDS,
);
Expand Down Expand Up @@ -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,
),
Expand All @@ -306,6 +312,7 @@ export default function MyPage() {
JSON.stringify({ ...readProfile(), ...next }),
);
setDraftProfileImageFile(null);
setShouldResetProfileImage(false);
setEditing(false);
setStatusMessage("ํ”„๋กœํ•„์„ ์ˆ˜์ •ํ–ˆ์–ด์š”.");
} catch (profileError) {
Expand All @@ -323,20 +330,31 @@ export default function MyPage() {
setDraftNickname(profile.nickname || "Quespot ํƒํ—˜๊ฐ€");
setDraftProfileImage(profile.profileImageUrl || questyProfile);
setDraftProfileImageFile(null);
setShouldResetProfileImage(false);
setEditing(true);
};

const handleProfileImageChange = (event: ChangeEvent<HTMLInputElement>) => {
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);
reader.readAsDataURL(file);
event.target.value = "";
};

const handleResetProfileImage = () => {
setDraftProfileImage(questyProfile);
setDraftProfileImageFile(null);
setShouldResetProfileImage(true);
if (profileImageInputRef.current) {
profileImageInputRef.current.value = "";
}
};

const openTravelEditor = () => {
const availableInterests = profile.interests ?? [];
setDraftInterests(
Expand Down Expand Up @@ -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);
}
}}
/>
<span className="absolute -bottom-1 -right-1 grid h-8 w-8 place-items-center rounded-full border-[3px] border-white bg-[#50ace9] text-white">
<Camera size={14} />
Expand All @@ -1061,9 +1084,26 @@ export default function MyPage() {
onChange={handleProfileImageChange}
type="file"
/>
<span className="mt-2 text-[9px] font-bold text-[#8495a5]">
์‚ฌ์ง„ ๋ณ€๊ฒฝ
</span>
<div className="mt-2 flex items-center gap-2 text-[9px] font-bold">
<button
className="rounded-full px-2 py-1 text-[#8495a5] transition-colors hover:bg-[#f2f7fb] hover:text-[#50ace9] disabled:opacity-50"
disabled={isSaving}
onClick={() => profileImageInputRef.current?.click()}
type="button"
>
์‚ฌ์ง„ ๋ณ€๊ฒฝ
</button>
<span className="h-3 w-px bg-[#dce6ee]" aria-hidden="true" />
<button
className="flex items-center gap-1 rounded-full px-2 py-1 text-[#8495a5] transition-colors hover:bg-[#f2f7fb] hover:text-[#50ace9] disabled:opacity-50"
disabled={isSaving || shouldResetProfileImage}
onClick={handleResetProfileImage}
type="button"
>
<RotateCcw size={10} />
๊ธฐ๋ณธ ์ด๋ฏธ์ง€๋กœ ๋ณ€๊ฒฝ
</button>
</div>
</div>
<label className="grid gap-2 text-[11px] font-bold text-[#536071]">
๋‹‰๋„ค์ž„
Expand Down
Loading