diff --git a/src/pages/auth/LoginPage.tsx b/src/pages/auth/LoginPage.tsx
index 2d124373..8f8c399d 100644
--- a/src/pages/auth/LoginPage.tsx
+++ b/src/pages/auth/LoginPage.tsx
@@ -1,4 +1,4 @@
-import { AppleButton, KakaoButton } from "@/components/auth";
+import { AppleButton, KakaoButton, RecentLoginDialog } from "@/components/auth";
import { CTA_Button, Press } from "@/components/common";
import { Link, useNavigate, useSearchParams } from "react-router";
import { useEffect, useMemo, useState } from "react";
@@ -9,6 +9,11 @@ import { Capacitor3KakaoLogin } from "capacitor3-kakao-login";
import { isNativeApp } from "@/utils/auth/envUtils";
import { isAndroidApp } from "@/utils/platform";
import { oauth } from "@/apis/auth";
+import {
+ getRecentLoginProvider,
+ setPendingSignupProvider,
+ setRecentLoginProvider,
+} from "@/utils/auth/recentLoginProvider";
import { tokenStorage } from "@/utils/tokenStorage";
import { consumeRedirectAfterLogin } from "../demoDay/redirectAfterLogin";
import { SplashScreen } from "@capacitor/splash-screen"; // 앱 초기 스플래시 제어용
@@ -34,6 +39,9 @@ export function LoginPage() {
const [authCheckStatus, setAuthCheckStatus] =
useState
("pending");
+ // 최근에 로그인했던 SNS (버튼 위 안내용)
+ const [recentLoginProvider] = useState(() => getRecentLoginProvider());
+
// 앱 실행 시 네이티브 스플래시 숨김 & 백그라운드 토큰 검사
useEffect(() => {
// 앱인 경우 기본 제공되는 스플래시를 즉시 가리고 리액트 애니메이션 띄우기
@@ -156,12 +164,15 @@ export function LoginPage() {
const data = response.data;
if ("isNewMember" in data && data.isNewMember === true) {
+ setPendingSignupProvider("KAKAO");
await tokenStorage.setTokens({
accessToken: null,
signupToken: data.signupToken,
});
navigate("/auth/onboarding", { replace: true });
} else if ("member" in data) {
+ // 가입이 끝난 계정만 "최근 로그인"으로 기록 (신규 회원은 아직 계정이 없음)
+ setRecentLoginProvider("KAKAO");
await tokenStorage.setTokens({
accessToken: data.accessToken,
signupToken: null,
@@ -240,7 +251,7 @@ export function LoginPage() {
>
navigate("/mainpage", { replace: true })}
color="orange"
size="compact"
/>
@@ -259,9 +270,29 @@ export function LoginPage() {
key={ui.footerKey}
className={`mx-auto max-w-sm ${ui.footerAnim}`}
>
-
- {!isAndroidApp() && (
-
+
+ {recentLoginProvider === "APPLE" && !isAndroidApp() && (
+
+ )}
+
+ {!isAndroidApp() && (
+
+ )}
+ {recentLoginProvider === "KAKAO" && !isAndroidApp() && (
+
+ )}
+
+ {recentLoginProvider === "KAKAO" && isAndroidApp() && (
+
)}
diff --git a/src/pages/auth/OnBoarding.tsx b/src/pages/auth/OnBoarding.tsx
index b5d861b0..f253ba2b 100644
--- a/src/pages/auth/OnBoarding.tsx
+++ b/src/pages/auth/OnBoarding.tsx
@@ -3,6 +3,7 @@ import { useLocation, useNavigate } from "react-router";
import { formatMMSS } from "@/utils/time";
import { ActionButton, InputForm } from "@/components/auth";
import { Collapse, CTA_Button } from "@/components/common";
+import { DialogBox } from "@/components/common/DialogBox";
import { useOnBoardingForm } from "@/hooks/auth/onBoarding";
import type { TermsType } from "@/types/auth";
@@ -121,6 +122,15 @@ export function OnBoardingPage() {
onClick={f.handleSubmit}
/>
+
+
navigate("/auth/login", { replace: true })}
+ onCancel={f.closeDuplicatePhoneDialog}
+ />
);
}
diff --git a/src/pages/auth/TermsAgreementPage.tsx b/src/pages/auth/TermsAgreementPage.tsx
index 0154db43..5d7ca227 100644
--- a/src/pages/auth/TermsAgreementPage.tsx
+++ b/src/pages/auth/TermsAgreementPage.tsx
@@ -19,7 +19,10 @@ export function TermsAgreementPage() {
const handleConfirm = () => {
if (!requiredAllChecked) return;
- navigate("/auth/onboarding", { state: { agreedTermTypes } });
+ navigate("/auth/onboarding", {
+ state: { agreedTermTypes },
+ replace: true,
+ });
};
return (
diff --git a/src/utils/auth/recentLoginProvider.ts b/src/utils/auth/recentLoginProvider.ts
new file mode 100644
index 00000000..886f1aad
--- /dev/null
+++ b/src/utils/auth/recentLoginProvider.ts
@@ -0,0 +1,45 @@
+import type { SocialProvider } from "@/types/auth";
+
+const RECENT_LOGIN_PROVIDER_KEY = "finders:recentLoginProvider";
+const PENDING_SIGNUP_PROVIDER_KEY = "finders:pendingSignupProvider";
+
+// 로그아웃과 무관하게 남아야 해서 useAuthStore(persist)와 분리된 별도 키로 관리
+export function setRecentLoginProvider(provider: SocialProvider) {
+ try {
+ localStorage.setItem(RECENT_LOGIN_PROVIDER_KEY, provider);
+ } catch (e) {
+ console.error("최근 로그인 정보 저장에 실패했습니다.", e);
+ }
+}
+
+export function getRecentLoginProvider(): SocialProvider | null {
+ try {
+ const value = localStorage.getItem(RECENT_LOGIN_PROVIDER_KEY);
+ return value === "KAKAO" || value === "APPLE" ? value : null;
+ } catch {
+ return null;
+ }
+}
+
+// 신규 회원은 가입이 끝나야 "최근 로그인"으로 기록되므로, 그때까지 provider를 임시 보관한다.
+// (온보딩 시점엔 provider를 알 수 없고, 카카오 웹은 리다이렉트를 타므로 localStorage 사용)
+export function setPendingSignupProvider(provider: SocialProvider) {
+ try {
+ localStorage.setItem(PENDING_SIGNUP_PROVIDER_KEY, provider);
+ } catch (e) {
+ console.error("가입 중 소셜 정보 저장에 실패했습니다.", e);
+ }
+}
+
+// 가입 완료 시점에 임시 보관한 provider를 "최근 로그인"으로 확정한다.
+export function commitPendingSignupProvider() {
+ try {
+ const value = localStorage.getItem(PENDING_SIGNUP_PROVIDER_KEY);
+ localStorage.removeItem(PENDING_SIGNUP_PROVIDER_KEY);
+ if (value === "KAKAO" || value === "APPLE") {
+ setRecentLoginProvider(value);
+ }
+ } catch (e) {
+ console.error("최근 로그인 정보 저장에 실패했습니다.", e);
+ }
+}