diff --git a/.claude/settings.json b/.claude/settings.json index 0ef714b..a373c0b 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,4 +1,9 @@ { + "permissions": { + "permissions": { + "defaultMode": "bypassPermissions" + } + }, "hooks": { "SessionStart": [ { diff --git a/apps/web/public/changelog.json b/apps/web/public/changelog.json index 7fd3ba7..9606aa8 100644 --- a/apps/web/public/changelog.json +++ b/apps/web/public/changelog.json @@ -1,6 +1,7 @@ { "1.1.0": { "title": "버전 1.1.0", + "date": "2025-05-12", "message": [ "기존 노래방 데이터를 새로운 데이터로 교체하였습니다.", "- TJ 노래방의 38519개의 곡 데이터 업데이트!", @@ -9,6 +10,7 @@ }, "1.2.0": { "title": "버전 1.2.0", + "date": "2025-05-11", "message": [ "인기곡 페이지를 추가했습니다.", "- 전체, 연도별, 월별 부른 곡의 통계와 즐겨찾기 한 곡의 통계를 확인할 수 있습니다.", @@ -17,6 +19,7 @@ }, "1.3.0": { "title": "버전 1.3.0", + "date": "2025-05-23", "message": [ "저장 기능을 추가했습니다.", "로그인 하지 않고도 인기곡 페이지를 조회할 수 있습니다.", @@ -27,6 +30,7 @@ "1.4.0": { "title": "버전 1.4.0", + "date": "2025-05-24", "message": [ "스크롤 디자인 높이를 조정했습니다.", "부를곡 페이지에서 재생목록을 통해 노래를 추가할 수 있습니다.", @@ -35,6 +39,7 @@ }, "1.5.0": { "title": "버전 1.5.0", + "date": "2025-06-08", "message": [ "검색 페이지에서 무한 스크롤 기능이 추가되었습니다.", "검색 페이지에서 검색 기록 기능이 추가되었습니다.", @@ -43,6 +48,7 @@ }, "1.6.0": { "title": "버전 1.6.0", + "date": "2025-06-18", "message": [ "회원 탈퇴 기능이 추가되었습니다.", "화면 높이를 조정했습니다.", @@ -51,6 +57,7 @@ }, "1.7.0": { "title": "버전 1.7.0", + "date": "2025-08-23", "message": [ "화면 UX를 개선했습니다. 세부적인 디자인을 조정했습니다.", "로그인 상태에서 전체 검색 시 오류가 나는 현상을 수정했습니다." @@ -58,22 +65,27 @@ }, "1.8.0": { "title": "버전 1.8.0", + "date": "2025-09-15", "message": ["노래방에 최근 추가된 곡을 확인할 수 있는 페이지를 추가했습니다."] }, "1.8.1": { "title": "버전 1.8.1", + "date": "2025-12-12", "message": ["UI를 개선했습니다."] }, "1.9.0": { "title": "버전 1.9.0", + "date": "2026-01-04", "message": ["챗봇 기능이 추가되었습니다."] }, "1.9.1": { "title": "버전 1.9.1", + "date": "2026-01-12", "message": ["로그인 인증 이슈를 해결했습니다."] }, "2.0.0": { "title": "버전 2.0.0", + "date": "2026-01-26", "message": [ "출석 체크 기능을 추가했습니다.", "유저 별 포인트 기능을 추가했습니다.", @@ -83,6 +95,7 @@ }, "2.0.1": { "title": "버전 2.0.1", + "date": "2026-02-03", "message": [ "로컬 스토리지 저장 기능을 개선했습니다.", "검색 카드 디자인 및 기능을 개선했습니다." @@ -90,6 +103,7 @@ }, "2.1.0": { "title": "버전 2.1.0", + "date": "2026-02-08", "message": [ "비로그인 (Guest) 유저로 부를 곡 기능을 사용할 수 있습니다.", "하단 네비게이션 바에 애니메이션 효과를 추가하여 UX를 개선했습니다." @@ -97,6 +111,7 @@ }, "2.2.0": { "title": "버전 2.2.0", + "date": "2026-02-20", "message": [ "일본 가수를 한글로 검색할 수 있습니다. 초성으로도 가능합니다.", "유명 일본 가수를 한눈에 조회할 수 있습니다." @@ -104,6 +119,7 @@ }, "2.3.0": { "title": "버전 2.3.0", + "date": "2026-03-02", "message": [ "인기곡 페이지의 UI를 개선했습니다. 이제 인기곡 페이지에서 곡을 추천할 수 있습니다.", "로그인 시 제공되는 코인을 30개로 변경했습니다." @@ -111,10 +127,12 @@ }, "2.4.0": { "title": "버전 2.4.0", + "date": "2026-04-06", "message": ["네온 나이트 다크 테마를 추가했습니다.", "전체적인 UI를 개선했습니다."] }, "2.5.0": { "title": "버전 2.5.0", + "date": "2026-04-19", "message": ["일본 가수, 곡의 한글 번역을 추가했습니다."] } } diff --git a/apps/web/src/Sidebar.tsx b/apps/web/src/Sidebar.tsx index b6461c4..b7104f0 100644 --- a/apps/web/src/Sidebar.tsx +++ b/apps/web/src/Sidebar.tsx @@ -1,6 +1,6 @@ 'use client'; -import { Check, LogOut, Menu, Pencil, User, X } from 'lucide-react'; +import { Check, LogOut, Menu, Pencil, ScrollText, User, X } from 'lucide-react'; import Image from 'next/image'; import { useRouter } from 'next/navigation'; import { useState } from 'react'; @@ -57,6 +57,11 @@ export default function Sidebar() { setIsOpenSidebar(false); }; + const handleOpenPatchNotes = () => { + router.push('/patch-notes'); + setIsOpenSidebar(false); + }; + const handleLogin = () => { router.push('/login'); setIsOpenSidebar(false); @@ -129,7 +134,16 @@ export default function Sidebar() { -
+
+ +
diff --git a/apps/web/src/app/patch-notes/page.tsx b/apps/web/src/app/patch-notes/page.tsx new file mode 100644 index 0000000..151d606 --- /dev/null +++ b/apps/web/src/app/patch-notes/page.tsx @@ -0,0 +1,78 @@ +'use client'; + +import { ArrowLeft } from 'lucide-react'; +import { useRouter } from 'next/navigation'; + +import { Button } from '@/components/ui/button'; +import { ScrollArea } from '@/components/ui/scroll-area'; + +import changelog from '../../../public/changelog.json'; + +type ChangelogEntry = { title: string; date?: string; message: string[] }; + +const entries = Object.entries(changelog as Record).reverse(); + +export default function PatchNotesPage() { + const router = useRouter(); + + return ( +
+
+ +

패치노트

+
+ + +
    + + + {entries.map(([version, entry]) => ( +
  1. +
    + {entry.date ?? ''} +
    + +
    + + +
    + +
    +
    버전 {version}
    +
      + {entry.message.map((line, idx) => { + const isSub = line.startsWith('-'); + return ( +
    • + {isSub ? line : `• ${line}`} +
    • + ); + })} +
    +
    +
  2. + ))} +
+
+
+ ); +} diff --git a/apps/web/src/app/search/SearchHistory.tsx b/apps/web/src/app/search/SearchHistory.tsx index 5e3091d..d1f6dbf 100644 --- a/apps/web/src/app/search/SearchHistory.tsx +++ b/apps/web/src/app/search/SearchHistory.tsx @@ -9,13 +9,6 @@ interface SearchHistoryProps { export default function SearchHistory({ onHistoryClick }: SearchHistoryProps) { const { searchHistory, removeFromHistory } = useSearchHistoryStore(); - const [isHydrated, setIsHydrated] = useState(false); - - useEffect(() => { - if (searchHistory.length > 0) { - setIsHydrated(true); - } - }, [searchHistory]); return (
@@ -23,34 +16,34 @@ export default function SearchHistory({ onHistoryClick }: SearchHistoryProps) {

최근 검색어

- {!isHydrated ? ( -
- -
- ) : ( -
- {searchHistory.slice(0, 10).map((term, index) => ( -
+ {searchHistory.length === 0 && ( +
+ 최근 검색어가 없습니다. +
+ )} + {searchHistory.slice(0, 10).map((term, index) => ( +
+ onHistoryClick(term)} > - onHistoryClick(term)} - > - {term} - - removeFromHistory(term)} - title="검색 기록 삭제" - > - - -
- ))} -
- )} + {term} + + removeFromHistory(term)} + title="검색 기록 삭제" + > + + +
+ ))} + ); } diff --git a/apps/web/src/auth.tsx b/apps/web/src/auth.tsx index ec511c0..bf07ce0 100644 --- a/apps/web/src/auth.tsx +++ b/apps/web/src/auth.tsx @@ -13,6 +13,7 @@ const ALLOW_PATHS = [ '/recent', '/tosing', '/update-password', + '/patch-notes', ]; export default function AuthProvider({ children }: { children: React.ReactNode }) {