Skip to content
738 changes: 356 additions & 382 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Metadata } from 'next';
import { Geist, Geist_Mono } from 'next/font/google';
import '@/styles/globals.css';
import ClientHeader from '@/components/ClientHeader';

const geistSans = Geist({
variable: '--font-geist-sans',
Expand All @@ -27,6 +28,10 @@ export default function RootLayout({
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased bg-gray-50 text-gray-900`}
>
{/* 전역 헤더 */}
<ClientHeader />

{/* 메인 컨텐츠 */}
{children}
</body>
</html>
Expand Down
133 changes: 2 additions & 131 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,63 +1,19 @@
'use client';

import Header from '@/components/ui/Header';
import SearchForm from '@/components/ServerSearchForm';
import { useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react';
import React from 'react';
import KoreanStandardTime from '@/components/KoreanStandardTime';
import LoginModal from '@/components/auth/LoginModal';
import SignupModal from '@/components/auth/SignupModal';
import ConfirmModal from '@/components/ui/ConfirmModal';

export default function Home() {
const router = useRouter();
const [signupOpen, setSignupOpen] = useState(false);
const [loginOpen, setLoginOpen] = useState(false);
const [isAuthed, setIsAuthed] = useState(false);
const [userName, setUserName] = useState<string | undefined>(undefined);
const [confirmOpen, setConfirmOpen] = useState(false);

const handleSubmit = (url: string) => {
router.push(`/result?url=${encodeURIComponent(url)}`);
};

// 새로고침 시에도 로그인 유지
useEffect(() => {
const at = localStorage.getItem('accessToken');
const name = localStorage.getItem('userName') || undefined;
if (at) {
setIsAuthed(true);
setUserName(name);
}
}, []);

const handleLogout = () => {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
localStorage.removeItem('userName');
setIsAuthed(false);
setUserName(undefined);
setConfirmOpen(false);

alert('로그아웃 되었습니다.');
router.push('/'); // 홈으로 리다이렉트
};

const headerProps: React.ComponentProps<typeof Header> = isAuthed
? {
isAuthed: true as const,
userName: userName!,
onLogoutClick: () => setConfirmOpen(true),
}
: {
onLoginClick: () => setLoginOpen(true),
};

return (
<div className="min-h-screen bg-gray-50 text-gray-900">
{/* Header */}
<Header {...headerProps} />

{' '}
{/* Hero */}
<section className="text-center py-16">
<div className="inline-flex items-center gap-2 bg-indigo-100 text-indigo-600 text-sm font-medium px-3 py-1 rounded-full mb-6">
Expand All @@ -73,99 +29,14 @@ export default function Home() {
성공하세요.
</p>
</section>

{/* URL Input */}
<section className="max-w-xl mx-auto">
<SearchForm onSubmit={handleSubmit} />
</section>

{/* Current Time */}
<section className="max-w-3xl mx-auto mb-20 p-10">
<KoreanStandardTime showToggle={false} />
</section>

{/* 로그인 모달 */}
<LoginModal
open={loginOpen}
onClose={() => setLoginOpen(false)}
onSignupClick={() => {
setLoginOpen(false);
setSignupOpen(true);
}}
onSubmit={async ({ email, password }) => {
try {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_BASE}/api/auth/login`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
},
);

const data = await res.json();

if (!res.ok) throw new Error(data.error || '로그인 실패');

localStorage.setItem('accessToken', data.data.accessToken);
localStorage.setItem('refreshToken', data.data.refreshToken);
if (data?.data?.user?.username) {
localStorage.setItem('userName', data.data.user.username);
setUserName(data.data.user.username);
}
setIsAuthed(true);
return true;
} catch (err) {
alert(err instanceof Error ? err.message : '로그인 중 오류 발생');
return false; // 실패 시 false 반환
}
}}
/>

{/* 회원가입 모달 */}
<SignupModal
open={signupOpen}
onClose={() => setSignupOpen(false)}
onLoginClick={() => {
setSignupOpen(false);
setLoginOpen(true);
}}
onSubmit={async ({ username, email, password }) => {
try {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_BASE}/api/auth/register`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, email, password }),
},
);
const data = await res.json();

if (!res.ok) {
throw new Error(data.error || '회원가입 실패');
}

console.log('회원가입 성공', data.data.user);
alert('회원가입이 완료되었습니다. 로그인 해주세요.');
setSignupOpen(false);
setLoginOpen(true); // 바로 로그인 유도
} catch (err) {
alert(err instanceof Error ? err.message : '회원가입 중 오류 발생');
}
}}
/>

{/* 로그아웃 확인 모달 */}
<ConfirmModal
open={confirmOpen}
title="로그아웃 확인"
message="정말 로그아웃하시겠습니까?"
confirmText="로그아웃"
cancelText="취소"
onConfirm={handleLogout}
onClose={() => setConfirmOpen(false)}
/>
</div>
);
}
Loading