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
32 changes: 24 additions & 8 deletions src/app/auth/callback/page.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,51 @@
'use client';

import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useRouter, useSearchParams } from 'next/navigation';
import { supabase } from '@/lib/supabase';
import { Suspense } from 'react';

export default function AuthCallback() {
function AuthCallbackInner() {
const router = useRouter();
const searchParams = useSearchParams();

useEffect(() => {
// 카카오 로그인 취소 시 error=access_denied 쿼리가 붙어서 돌아옴
const errorParam = searchParams.get('error');
if (errorParam) {
router.replace('/');
return;
}

const handleAuth = async () => {
// 1. URL에 포함된 토큰을 이용해 세션을 가져옵니다.
// 이 과정에서 Supabase 라이브러리가 브라우저 쿠키/로컬스토리지에 로그인 정보를 저장합니다.
const { data, error } = await supabase.auth.getSession();

if (data.session) {
// 2. 로그인이 확인되면 메인 페이지('/')로 이동시킵니다.
// router.replace를 쓰면 주소창이 깔끔하게 '/'로 바뀌고 뒤로가기가 방지됩니다.
router.replace('/');
} else if (error) {
console.error('로그인 처리 중 오류:', error.message);
router.replace('/');
} else {
// session도 error도 없으면 (ex. 직접 URL 접근) 홈으로
router.replace('/');
}
};

handleAuth();
}, [router]);
}, [router, searchParams]);

return (
<div className='flex h-screen w-full flex-col items-center justify-center gap-4'>
<div className='w-10 h-10 border-4 border-yellow-400 border-t-transparent rounded-full animate-spin' />
<p className='text-lg font-medium'>카카오로그인 중...</p>
<p className='text-lg font-medium'>카카오 로그인 중...</p>
</div>
);
}

export default function AuthCallback() {
return (
<Suspense>
<AuthCallbackInner />
</Suspense>
);
}
4 changes: 2 additions & 2 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export default async function Home() {
{/* 헤더 */}
<Header />

{/* 지도 */}
<section className='flex-1'>
{/* 지도 — contain-layout으로 내부 레이아웃이 외부에 영향주지 않도록 격리 */}
<section className='flex-1 contain-layout'>
<KakaoMap shops={shops} />
</section>

Expand Down
24 changes: 19 additions & 5 deletions src/app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
import { MetadataRoute } from 'next';
import { supabase } from '@/lib/supabase';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = 'https://gachamap.vercel.app';

const routes: MetadataRoute.Sitemap = [
const staticRoutes: MetadataRoute.Sitemap = [
{
url: baseUrl,
lastModified: new Date(),
lastModified: new Date('2025-01-01'),
changeFrequency: 'daily',
priority: 1,
},
{
url: `${baseUrl}/gacha-board`,
lastModified: new Date(),
lastModified: new Date('2025-01-01'),
changeFrequency: 'daily',
priority: 0.9,
},
{
url: `${baseUrl}/about`,
lastModified: new Date(),
lastModified: new Date('2025-01-01'),
changeFrequency: 'monthly',
priority: 0.5,
},
];

return routes;
// 가챠보드 게시글 동적 포함
const { data: posts } = await supabase
.from('gacha_posts')
.select('id, created_at')
.order('created_at', { ascending: false });

const postRoutes: MetadataRoute.Sitemap = (posts ?? []).map((post) => ({
url: `${baseUrl}/gacha-board/${post.id}`,
lastModified: new Date(post.created_at),
changeFrequency: 'weekly' as const,
priority: 0.7,
}));

return [...staticRoutes, ...postRoutes];
}
58 changes: 28 additions & 30 deletions src/components/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -230,38 +230,36 @@ export default function KakaoMap({ shops }: MapProps) {
setIsSuggestionModalOpen(true);
};

if (!isMapLoaded) {
return (
<div className='relative w-full h-[80vh] rounded-2xl flex items-center justify-center bg-gray-100'>
<div className='text-center'>
<svg
className='animate-spin h-12 w-12 text-blue-600 mx-auto mb-4'
xmlns='http://www.w3.org/2000/svg'
fill='none'
viewBox='0 0 24 24'
>
<circle
className='opacity-25'
cx='12'
cy='12'
r='10'
stroke='currentColor'
strokeWidth='4'
></circle>
<path
className='opacity-75'
fill='currentColor'
d='M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z'
></path>
</svg>
<p className='text-gray-600 font-medium'>지도를 불러오는 중...</p>
</div>
</div>
);
}

return (
<div className='relative w-full h-[80vh] rounded-2xl mb-10'>
{/* 지도 로딩 오버레이 — 컨테이너 크기는 항상 유지해 CLS 방지 */}
{!isMapLoaded && (
<div className='absolute inset-0 z-20 rounded-2xl flex items-center justify-center bg-gray-100'>
<div className='text-center'>
<svg
className='animate-spin h-12 w-12 text-blue-600 mx-auto mb-4'
xmlns='http://www.w3.org/2000/svg'
fill='none'
viewBox='0 0 24 24'
>
<circle
className='opacity-25'
cx='12'
cy='12'
r='10'
stroke='currentColor'
strokeWidth='4'
></circle>
<path
className='opacity-75'
fill='currentColor'
d='M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z'
></path>
</svg>
<p className='text-gray-600 font-medium'>지도를 불러오는 중...</p>
</div>
</div>
)}
{/* 카테고리 필터 */}
<div className='absolute top-4 left-0 right-0 z-10 px-4'>
<div className='flex gap-2 overflow-x-auto pb-2 scrollbar-hide'>
Expand Down