[보안/인증] - 모든 player/queue API 라우트에 세션 가드 추가 (이전: /api/servers 만 보호) - NextAuth 환경변수 부팅 시점 검증, NEXTAUTH_SECRET 명시 - next.config.ts CSP/보안 헤더 추가, 잘못된 allowedDevOrigins 제거 - Redis 호스트 하드코딩 IP 제거(필수 env 로 강제) [안정성] - 봇 RPC 패턴(@/lib/api) 공용화: crypto.randomUUID requestId, JSON.parse 안전, EXPIRE 자동, 폴링 백오프 - SSE(@/lib/sse) 공용화: subscriber error 처리, JSON.parse 안전, 30초 keep-alive, abort/에러 정리 - pause API 양 끝(boolean) 정상화: 프론트 String() 캐스트 + 백엔드 .trim().toLowerCase() 비교 제거 - 봇 RedisClient: isPaused/index/seek/volume falsy 거부 → typeof 검사로 교체(0/false 정상 허용) [타입/품질] - next-auth 모듈 보강 → session.user.id, session.accessToken 타입 안전 - DiscordServer/Track/SearchTrack 공용 타입 도입, 컴포넌트 any 제거 - BigInt permissions 안전 검증(타입 가드) - Logger: NODE_ENV 게이트, error → stderr, ISO 기반 안전 timestamp - tsconfig target → ES2020 (BigInt 리터럴) [취약점] - next 16.2.2 → 16.2.4 (DoS/postcss XSS 패치) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
67 lines
2.1 KiB
TypeScript
67 lines
2.1 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import TopNav from "@/components/layout/TopNav";
|
|
import LeftSidebar from "@/components/layout/LeftSidebar";
|
|
import MainContent from "@/components/player/MainContent";
|
|
import QueueSidebar from "@/components/player/QueueSidebar";
|
|
import PlayerBar from "@/components/player/PlayerBar";
|
|
import type { DiscordServer } from "@/types/music";
|
|
|
|
// 화면 모드 타입 정의
|
|
export type ViewMode = "SERVER_LIST" | "SERVER_DETAIL" | "SEARCH_RESULT";
|
|
|
|
export default function MusicPlayerLayout() {
|
|
const [viewMode, setViewMode] = useState<ViewMode>("SERVER_LIST");
|
|
const [selectedServer, setSelectedServer] = useState<DiscordServer | null>(null);
|
|
const [searchQuery, setSearchQuery] = useState("");
|
|
|
|
// 홈 버튼 클릭 시: 서버 목록(또는 상세)으로 복귀
|
|
const handleHome = () => {
|
|
if (selectedServer) {
|
|
setViewMode("SERVER_DETAIL");
|
|
} else {
|
|
setViewMode("SERVER_LIST");
|
|
setSelectedServer(null);
|
|
setSearchQuery("");
|
|
}
|
|
};
|
|
|
|
// 검색 실행 시
|
|
const handleSearch = (query: string) => {
|
|
setSearchQuery(query);
|
|
setViewMode("SEARCH_RESULT");
|
|
};
|
|
|
|
// 서버 선택 시
|
|
const handleSelectServer = (server: DiscordServer) => {
|
|
setSelectedServer(server);
|
|
setViewMode("SERVER_DETAIL");
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col h-screen bg-neutral-900 text-white overflow-hidden font-sans">
|
|
<TopNav
|
|
onSearch={handleSearch}
|
|
onHome={handleHome}
|
|
selectedServer={selectedServer} // 🌟 이 줄을 추가해서 선택된 서버 정보를 넘깁니다.
|
|
/>
|
|
|
|
<div className="flex flex-1 overflow-hidden">
|
|
<LeftSidebar />
|
|
<MainContent
|
|
viewMode={viewMode}
|
|
setViewMode={setViewMode}
|
|
selectedServer={selectedServer}
|
|
setSelectedServer={setSelectedServer}
|
|
searchQuery={searchQuery}
|
|
setSearchQuery={setSearchQuery}
|
|
onSelectServer={handleSelectServer}
|
|
/>
|
|
<QueueSidebar selectedServer={selectedServer} />
|
|
</div>
|
|
|
|
<PlayerBar selectedServer={selectedServer} />
|
|
</div>
|
|
);
|
|
} |