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
192 changes: 2 additions & 190 deletions src/app/bookmarks/page.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,23 @@
'use client';

import { useState, useEffect } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { Bookmark, BookmarkFormData } from '@/types/bookmark';
import { BookmarkAPI } from '@/libs/api/bookmarks';
import BookmarkItem from '@/components/bookmarks/BookmarkItem';
import BookmarkModal from '@/components/bookmarks/BookmarkModal';
import LoginModal from '@/components/auth/LoginModal';
import SignupModal from '@/components/auth/SignupModal';
import ConfirmModal from '@/components/ui/ConfirmModal';

export default function BookmarksPage() {
const router = useRouter();
const [bookmarks, setBookmarks] = useState<Bookmark[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [searchQuery, setSearchQuery] = useState('');
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');

// Auth states
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 [selectedBookmark, setSelectedBookmark] = useState<Bookmark | null>(null);
const [logoutConfirmOpen, setLogoutConfirmOpen] = useState(false);

// ๋ชจ๋‹ฌ ์ƒํƒœ
const [isModalOpen, setIsModalOpen] = useState(false);
Expand All @@ -36,10 +27,8 @@ export default function BookmarksPage() {
// ์ƒˆ๋กœ๊ณ ์นจ ์‹œ์—๋„ ๋กœ๊ทธ์ธ ์œ ์ง€
useEffect(() => {
const at = localStorage.getItem('accessToken');
const name = localStorage.getItem('userName') || undefined;
if (at) {
setIsAuthed(true);
setUserName(name);
loadBookmarks();
} else {
setLoading(false);
Expand All @@ -60,9 +49,8 @@ export default function BookmarksPage() {
localStorage.removeItem('refreshToken');
localStorage.removeItem('userName');
setIsAuthed(false);
setUserName(undefined);
setBookmarks([]);
setLoginOpen(true);
alert('๋กœ๊ทธ์ธ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•ด์ฃผ์„ธ์š”.');
} else {
setError(msg);
}
Expand All @@ -83,7 +71,7 @@ export default function BookmarksPage() {
// ๋ถ๋งˆํฌ ์ถ”๊ฐ€
const handleAdd = () => {
if (!isAuthed) {
setLoginOpen(true);
alert('๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.');
return;
}
setEditingBookmark(undefined);
Expand Down Expand Up @@ -156,17 +144,6 @@ export default function BookmarksPage() {
}
};

const handleLogout = () => {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
localStorage.removeItem('userName');
setIsAuthed(false);
setUserName(undefined);
setLogoutConfirmOpen(false);
setBookmarks([]);
alert('๋กœ๊ทธ์•„์›ƒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
router.push('/');
};

if (loading) {
return (
Expand All @@ -178,74 +155,6 @@ export default function BookmarksPage() {

return (
<div className="min-h-screen bg-gray-50">
{/* ํ—ค๋” */}
<header className="bg-white/80 backdrop-blur-xl border-b border-black/5 sticky top-0 z-50">
<div className="max-w-7xl mx-auto flex items-center justify-between px-6 py-3">
<Link href="/" className="flex items-center gap-2 text-black font-semibold text-base no-underline">
<div className="w-7 h-7 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-md flex items-center justify-center text-white text-sm">
โฐ
</div>
Check Time
</Link>

<nav className="flex items-center gap-8">
<a
href="/ranking"
className="text-gray-600 text-sm font-medium hover:text-black transition-colors no-underline"
>
์‹ค์‹œ๊ฐ„ ๋žญํ‚น
</a>
<a
href="/reaction-test"
className="text-gray-600 text-sm font-medium hover:text-black transition-colors no-underline"
>
๋ฐ˜์‘์†๋„ ๊ฒŒ์ž„
</a>
<a
href="/bookmarks"
className="text-black text-sm font-semibold no-underline"
>
๋ถ๋งˆํฌ
</a>
<a
href="/help"
className="text-gray-600 text-sm font-medium hover:text-black transition-colors no-underline"
>
๋„์›€๋ง
</a>
</nav>

<div className="flex items-center gap-3">
{isAuthed ? (
<>
<span className="text-sm text-gray-600">์•ˆ๋…•ํ•˜์„ธ์š”, {userName}๋‹˜</span>
<button
onClick={() => setLogoutConfirmOpen(true)}
className="px-4 py-2 text-gray-600 text-sm font-medium rounded-md hover:text-black hover:bg-black/5 transition-all"
>
๋กœ๊ทธ์•„์›ƒ
</button>
</>
) : (
<>
<button
onClick={() => setLoginOpen(true)}
className="px-4 py-2 text-gray-600 text-sm font-medium rounded-md hover:text-black hover:bg-black/5 transition-all"
>
๋กœ๊ทธ์ธ
</button>
<button
onClick={() => setSignupOpen(true)}
className="px-4 py-2 bg-black text-white text-sm font-medium rounded-md hover:bg-black/80 transition-all"
>
ํšŒ์›๊ฐ€์ž…
</button>
</>
)}
</div>
</div>
</header>

{/* ๋ฉ”์ธ ์ปจํ…์ธ  */}
<main className="max-w-7xl mx-auto px-6 py-10">
{/* ์ปจํŠธ๋กค ๋ฐ” */}
Expand Down Expand Up @@ -302,20 +211,6 @@ export default function BookmarksPage() {
๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค
</h3>
<p className="text-gray-600 mb-4">๋ถ๋งˆํฌ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋กœ๊ทธ์ธํ•ด์ฃผ์„ธ์š”</p>
<div className="flex gap-3 justify-center">
<button
onClick={() => setLoginOpen(true)}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
>
๋กœ๊ทธ์ธ
</button>
<button
onClick={() => setSignupOpen(true)}
className="px-4 py-2 bg-gray-600 text-white rounded-md hover:bg-gray-700 transition-colors"
>
ํšŒ์›๊ฐ€์ž…
</button>
</div>
</div>
) : error ? (
<div className="text-center py-12">
Expand Down Expand Up @@ -366,79 +261,6 @@ export default function BookmarksPage() {
isLoading={modalLoading}
/>

{/* ๋กœ๊ทธ์ธ ๋ชจ๋‹ฌ */}
<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);
setLoginOpen(false);
loadBookmarks();
return true;
} catch (err) {
alert(err instanceof Error ? err.message : '๋กœ๊ทธ์ธ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ');
return 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
Expand All @@ -454,16 +276,6 @@ export default function BookmarksPage() {
}}
/>

{/* ๋กœ๊ทธ์•„์›ƒ ํ™•์ธ ๋ชจ๋‹ฌ */}
<ConfirmModal
open={logoutConfirmOpen}
title="๋กœ๊ทธ์•„์›ƒ ํ™•์ธ"
message="์ •๋ง ๋กœ๊ทธ์•„์›ƒํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?"
confirmText="๋กœ๊ทธ์•„์›ƒ"
cancelText="์ทจ์†Œ"
onConfirm={handleLogout}
onClose={() => setLogoutConfirmOpen(false)}
/>
</div>
);
}
10 changes: 5 additions & 5 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
'use client';

import SearchForm from '@/components/ServerSearchForm';
import ServerSearchForm from '@/components/search-result/ServerSearchForm';
import { useRouter } from 'next/navigation';
import React from 'react';
import KoreanStandardTime from '@/components/KoreanStandardTime';
import KoreanStandardTime from '@/components/search-result/KoreanStandardTime';

export default function Home() {
const router = useRouter();
const handleSubmit = (url: string) => {
router.push(`/result?url=${encodeURIComponent(url)}`);
router.push(`/search-result?url=${encodeURIComponent(url)}`);
};

return (
Expand All @@ -26,12 +26,12 @@ export default function Home() {
</h1>
<p className="text-gray-600 text-lg max-w-2xl mx-auto leading-relaxed">
์„œ๋ฒ„์™€์˜ ์‹œ๊ฐ„ ์ฐจ์ด๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ํ™•์ธํ•˜๊ณ , ์™„๋ฒฝํ•œ ํƒ€์ด๋ฐ์œผ๋กœ ํ‹ฐ์ผ“ํŒ…์—
์„ฑ๊ณตํ•˜์„ธ์š”.
์„ฑ๊ณตํ•˜์„ธ์š”!
</p>
</section>
{/* URL Input */}
<section className="max-w-xl mx-auto">
<SearchForm onSubmit={handleSubmit} />
<ServerSearchForm onSubmit={handleSubmit} />
</section>
{/* Current Time */}
<section className="max-w-3xl mx-auto mb-20 p-10">
Expand Down
Loading