Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/app/menu/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const MenuItemCard: React.FC<MenuItemCardProps> = ({

const originalPhoto = photoData?.result?.content?.[0];
const imageUrl = food.thumbnailUrl && originalPhoto
? `${process.env.NEXT_PUBLIC_IMAGE_URL}/${food.thumbnailUrl}?s=80x80&t=crop&q=70`
? `${process.env.NEXT_PUBLIC_IMAGE_URL}/${food.thumbnailUrl}?s=300x300&t=crop&q=100`
: null;

const formatPrice = (price: number) => {
Expand Down
155 changes: 149 additions & 6 deletions src/components/auth/LoginGuard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,168 @@ export function LoginGuard({ children }: LoginGuardProps) {
window.location.reload();
};

// 토큰이 없으면 네이티브 로그인 호출
if (!isLoggedIn || !tokens) {
// 로딩 중이거나 토큰이 없으면 적절한 UI 표시
if (userInfoLoading || (isLoggedIn === false && !tokens)) {
const loadingText = userInfoLoading
? "로그인 상태를 확인하는 중..."
: "로그인 화면으로 이동 중...";

return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">로그인 화면으로 이동 중...</p>
<p className="text-gray-600">{loadingText}</p>
</div>
</div>
);
}

// 토큰이 있으면 로딩 후 홈화면
if (userInfoLoading) {
// 토큰이 없고 로딩이 끝났으면 로그인 필요 화면 표시
if (!isLoggedIn || !tokens) {
// 기존 로그인 필요 화면으로 바로 이동
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center px-4">
<Card className="w-full max-w-md p-8 text-center">
<div className="mb-6">
<div className="w-16 h-16 bg-blue-100 rounded-full flex items-center justify-center mx-auto mb-4">
<FontAwesomeIcon icon={faUser} className="text-2xl text-blue-600" />
</div>
<h1 className="text-2xl font-bold text-gray-900 mb-2">
로그인이 필요합니다
</h1>
<p className="text-gray-600">
{isAvailable
? "앱에서 로그인 후 다시 시도해주세요"
: "Chalpu 앱에서 로그인 후 이용해주세요"}
</p>
</div>

{/* 인증 에러 표시 */}
{userInfoError && (
<div className="mb-4 p-3 bg-red-100 border border-red-300 rounded-lg">
<p className="text-sm text-red-800">
로그인 정보 확인 실패:{" "}
{userInfoError.message || "알 수 없는 오류"}
</p>
<Button
onClick={handleRetryAuth}
variant="outline"
size="sm"
className="mt-2"
>
다시 시도
</Button>
</div>
)}

<div className="space-y-4">
<div className="bg-blue-100 border border-blue-300 rounded-lg p-4">
<p className="text-sm text-blue-800">
{isAvailable
? "📱 앱에서 로그인을 완료한 후 웹뷰로 이동해주세요"
: "📱 Chalpu 앱을 다운로드하여 로그인해주세요"}
</p>
</div>
</div>

{process.env.NODE_ENV === "development" && (
<div className="mt-6 pt-4 border-t border-gray-200">
<p className="text-xs text-gray-500 mb-3">개발 모드</p>

{/* 환경 정보 섹션 */}
<div className="mb-4 p-3 bg-gray-100 rounded-lg">
<p className="text-xs font-medium text-gray-700 mb-2">
🔍 환경 정보
</p>
<div className="text-xs text-gray-600 space-y-1">
<div className="flex justify-between">
<span>플랫폼:</span>
<span
className={
isAvailable
? "text-green-600 font-medium"
: "text-blue-600 font-medium"
}
>
{isAvailable ? "📱 네이티브 앱" : "🌐 웹 브라우저"}
</span>
</div>
<div className="flex justify-between">
<span>유저 정보:</span>
<span
className={
userInfoError
? "text-red-600"
: userInfo
? "text-green-600"
: "text-yellow-600"
}
>
{userInfoError
? "❌ 실패"
: userInfo
? "✅ 성공"
: "⏳ 로딩중"}
</span>
</div>
<div className="flex justify-between">
<span>에러:</span>
<span
className={
userInfoError ? "text-red-600" : "text-green-600"
}
>
{userInfoError ? "❌ 있음" : "✅ 없음"}
</span>
</div>
</div>
</div>

<div className="space-y-2">
<Button
onClick={handleDevLogin}
variant="outline"
className="w-full text-xs"
size="sm"
>
임시 로그인 (테스트용)
</Button>
<Button
onClick={clearTokens}
variant="outline"
className="w-full text-xs"
size="sm"
>
로그아웃 (테스트용)
</Button>
{isAvailable && (
<Button
onClick={handleRetryAuth}
variant="outline"
className="w-full text-xs"
size="sm"
>
인증 재시도
</Button>
)}
</div>
<p className="text-xs text-gray-400 mt-2">
앱에서 로그인 후 웹뷰로 이동하면 유저 정보가 자동으로 로드됩니다
</p>
</div>
)}
</Card>
</div>
);
}

// 토큰이 있는데 여전히 사용자 정보 로딩 중
if (userInfoLoading && (isLoggedIn || tokens)) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">로그인 상태를 확인하는 중...</p>
<p className="text-gray-600">사용자 정보를 불러오는 중...</p>
</div>
</div>
);
Expand Down
7 changes: 5 additions & 2 deletions src/hooks/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ export const useAuth = () => {
} catch (error) {
console.error("토큰 초기화 실패:", error);
clearTokens();
} finally {
setLoading(false);
}

// 초기화 실패 시 안전장치: 3초 후 강제로 로딩 해제
setTimeout(() => {
setLoading(false);
}, 3000);
}, [
setLoading,
clearTokens,
Expand Down
49 changes: 28 additions & 21 deletions src/store/useAuthStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,33 @@ export const useAuthStore = create<AuthState>()(

// 로컬스토리지에서 accessToken 확인 및 자동 로그인
initializeFromLocalStorage: () => {
const accessToken = localStorage.getItem("accessToken");
if (accessToken) {
const tokenObject = {
accessToken: accessToken,
refreshToken: "",
expiresIn: 3600,
tokenType: "Bearer",
};
set({
tokens: tokenObject,
isLoggedIn: true,
isLoading: false,
});
// 토큰 생성 시간이 없으면 현재 시간으로 설정
if (!localStorage.getItem("auth-storage-timestamp")) {
localStorage.setItem(
"auth-storage-timestamp",
Date.now().toString()
);
try {
const accessToken = localStorage.getItem("accessToken");
if (accessToken) {
const tokenObject = {
accessToken: accessToken,
refreshToken: "",
expiresIn: 3600,
tokenType: "Bearer",
};
set({
tokens: tokenObject,
isLoggedIn: true,
isLoading: false,
});
// 토큰 생성 시간이 없으면 현재 시간으로 설정
if (!localStorage.getItem("auth-storage-timestamp")) {
localStorage.setItem(
"auth-storage-timestamp",
Date.now().toString()
);
}
} else {
set({ isLoading: false, isLoggedIn: false, tokens: null });
}
} else {
set({ isLoading: false });
} catch (error) {
console.error('토큰 초기화 실패:', error);
set({ isLoading: false, isLoggedIn: false, tokens: null });
}
},

Expand All @@ -80,6 +85,8 @@ export const useAuthStore = create<AuthState>()(
onRehydrateStorage: () => (state) => {
if (state) {
state.setLoading(false);
// 초기 설치시 토큰 초기화 확실히 수행
setTimeout(() => state.initializeFromLocalStorage(), 0);
}
},
}
Expand Down