From 675014166a65601214f0b856fa0fb654747d643a Mon Sep 17 00:00:00 2001 From: corpi Date: Mon, 9 Mar 2026 13:05:44 +0900 Subject: [PATCH] =?UTF-8?q?refactor(space):=20scene=20=EB=8F=84=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=EA=B3=BC=20current=20session=20=EA=B3=84=EC=95=BD?= =?UTF-8?q?=EC=9D=84=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/room/index.ts | 2 - src/entities/scene/index.ts | 2 + .../model/rooms.ts => scene/model/scenes.ts} | 60 +++++----- src/entities/{room => scene}/model/types.ts | 10 +- src/entities/session/model/mockSession.ts | 8 +- src/entities/session/model/types.ts | 2 +- src/entities/session/model/useThoughtInbox.ts | 44 ++++++-- .../focus-session/api/focusSessionApi.ts | 34 +++--- .../model/useFocusSessionEngine.ts | 10 +- src/features/inbox/ui/InboxList.tsx | 2 +- src/features/scene-select/index.ts | 1 + .../ui/SceneSelectCarousel.tsx} | 34 +++--- src/features/space-select/index.ts | 1 - src/shared/lib/apiClient.ts | 13 ++- .../ui/ControlCenterSheetWidget.tsx | 36 +++--- .../ui/SpaceSetupDrawerWidget.tsx | 36 +++--- .../ui/SpaceToolsDockWidget.tsx | 22 ++-- .../ui/panels/SettingsToolPanel.tsx | 28 ++--- .../ui/SpaceWorkspaceWidget.tsx | 106 +++++++++--------- 19 files changed, 243 insertions(+), 208 deletions(-) delete mode 100644 src/entities/room/index.ts create mode 100644 src/entities/scene/index.ts rename src/entities/{room/model/rooms.ts => scene/model/scenes.ts} (86%) rename src/entities/{room => scene}/model/types.ts (69%) create mode 100644 src/features/scene-select/index.ts rename src/features/{space-select/ui/SpaceSelectCarousel.tsx => scene-select/ui/SceneSelectCarousel.tsx} (67%) delete mode 100644 src/features/space-select/index.ts diff --git a/src/entities/room/index.ts b/src/entities/room/index.ts deleted file mode 100644 index 4f02999..0000000 --- a/src/entities/room/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './model/rooms'; -export * from './model/types'; diff --git a/src/entities/scene/index.ts b/src/entities/scene/index.ts new file mode 100644 index 0000000..e66091f --- /dev/null +++ b/src/entities/scene/index.ts @@ -0,0 +1,2 @@ +export * from './model/scenes'; +export * from './model/types'; diff --git a/src/entities/room/model/rooms.ts b/src/entities/scene/model/scenes.ts similarity index 86% rename from src/entities/room/model/rooms.ts rename to src/entities/scene/model/scenes.ts index b8f6892..521537a 100644 --- a/src/entities/room/model/rooms.ts +++ b/src/entities/scene/model/scenes.ts @@ -1,5 +1,5 @@ import type { CSSProperties } from 'react'; -import type { RoomTheme } from './types'; +import type { SceneTheme } from './types'; const HUB_CURATION_ORDER = [ 'quiet-library', @@ -9,9 +9,9 @@ const HUB_CURATION_ORDER = [ 'fireplace', ] as const; -const HUB_RECOMMENDED_ROOM_COUNT = 3; +const HUB_RECOMMENDED_SCENE_COUNT = 3; -export const ROOM_THEMES: RoomTheme[] = [ +export const SCENE_THEMES: SceneTheme[] = [ { id: 'rain-window', name: '비 오는 창가', @@ -234,65 +234,65 @@ export const ROOM_THEMES: RoomTheme[] = [ }, ]; -export const getRoomById = (roomId: string) => { - return ROOM_THEMES.find((room) => room.id === roomId); +export const getSceneById = (id: string) => { + return SCENE_THEMES.find((scene) => scene.id === id); }; -export const getRoomCardPhotoUrl = (room: RoomTheme) => { +export const getSceneCardPhotoUrl = (scene: SceneTheme) => { // Swap managedCardPhotoUrl when you start serving first-party assets. - return room.managedCardPhotoUrl ?? room.cardPhotoUrl; + return scene.managedCardPhotoUrl ?? scene.cardPhotoUrl; }; -export const getRoomCardBackgroundStyle = (room: RoomTheme): CSSProperties => { +export const getSceneCardBackgroundStyle = (scene: SceneTheme): CSSProperties => { return { - backgroundImage: `url('${getRoomCardPhotoUrl(room)}')`, + backgroundImage: `url('${getSceneCardPhotoUrl(scene)}')`, backgroundSize: 'cover', backgroundPosition: 'center', }; }; -export const getRoomBackgroundStyle = (room: RoomTheme): CSSProperties => { +export const getSceneBackgroundStyle = (scene: SceneTheme): CSSProperties => { return { - backgroundImage: `url('${getRoomCardPhotoUrl(room)}'), linear-gradient(160deg, #1e293b 0%, #0f172a 100%)`, + backgroundImage: `url('${getSceneCardPhotoUrl(scene)}'), linear-gradient(160deg, #1e293b 0%, #0f172a 100%)`, backgroundSize: 'cover, cover', backgroundPosition: 'center, center', backgroundRepeat: 'no-repeat, no-repeat', }; }; -const uniqueByRoomId = (rooms: Array) => { +const uniqueBySceneId = (scenes: Array) => { const seen = new Set(); - return rooms.filter((room): room is RoomTheme => { - if (!room || seen.has(room.id)) { + return scenes.filter((scene): scene is SceneTheme => { + if (!scene || seen.has(scene.id)) { return false; } - seen.add(room.id); + seen.add(scene.id); return true; }); }; -export const getHubRoomSections = ( - rooms: RoomTheme[], - selectedRoomId: string, - recommendedCount = HUB_RECOMMENDED_ROOM_COUNT, +export const getHubSceneSections = ( + scenes: SceneTheme[], + selectedSceneId: string, + recommendedCount = HUB_RECOMMENDED_SCENE_COUNT, ) => { - const roomById = new Map(rooms.map((room) => [room.id, room] as const)); - const selectedRoom = roomById.get(selectedRoomId); - const curatedRooms = HUB_CURATION_ORDER.map((id) => roomById.get(id)); + const sceneById = new Map(scenes.map((scene) => [scene.id, scene] as const)); + const selectedScene = sceneById.get(selectedSceneId); + const curatedScenes = HUB_CURATION_ORDER.map((id) => sceneById.get(id)); - const recommendedRooms = uniqueByRoomId([ - selectedRoom, - ...curatedRooms, - ...rooms, + const recommendedScenes = uniqueBySceneId([ + selectedScene, + ...curatedScenes, + ...scenes, ]).slice(0, recommendedCount); - const recommendedRoomIds = new Set(recommendedRooms.map((room) => room.id)); - const allRooms = [...recommendedRooms, ...rooms.filter((room) => !recommendedRoomIds.has(room.id))]; + const recommendedSceneIds = new Set(recommendedScenes.map((scene) => scene.id)); + const allScenes = [...recommendedScenes, ...scenes.filter((scene) => !recommendedSceneIds.has(scene.id))]; return { - recommendedRooms, - allRooms, + recommendedScenes, + allScenes, }; }; diff --git a/src/entities/room/model/types.ts b/src/entities/scene/model/types.ts similarity index 69% rename from src/entities/room/model/types.ts rename to src/entities/scene/model/types.ts index 99a6a32..4177335 100644 --- a/src/entities/room/model/types.ts +++ b/src/entities/scene/model/types.ts @@ -1,16 +1,16 @@ -export type RoomTag = '저자극' | '움직임 적음' | '딥워크' | '감성'; +export type SceneTag = '저자극' | '움직임 적음' | '딥워크' | '감성'; -export interface RoomPresence { +export interface ScenePresence { focus: number; break: number; away: number; } -export interface RoomTheme { +export interface SceneTheme { id: string; name: string; description: string; - tags: RoomTag[]; + tags: SceneTag[]; recommendedSound: string; recommendedSoundPresetId: string; recommendedTimerPresetId: string; @@ -21,7 +21,7 @@ export interface RoomTheme { googleImageSearchUrl: string; managedCardPhotoUrl: string | null; activeMembers: number; - presence: RoomPresence; + presence: ScenePresence; previewImage: string; previewGradient: string; } diff --git a/src/entities/session/model/mockSession.ts b/src/entities/session/model/mockSession.ts index bbbdc6c..7f9a3fa 100644 --- a/src/entities/session/model/mockSession.ts +++ b/src/entities/session/model/mockSession.ts @@ -72,25 +72,25 @@ export const RECENT_THOUGHTS: RecentThought[] = [ { id: 'thought-1', text: '내일 미팅 전에 제안서 첫 문단만 다시 다듬기', - roomName: '도서관', + sceneName: '도서관', capturedAt: '방금 전', }, { id: 'thought-2', text: '기획 문서의 핵심 흐름을 한 문장으로 정리해두기', - roomName: '비 오는 창가', + sceneName: '비 오는 창가', capturedAt: '24분 전', }, { id: 'thought-3', text: '오후에 확인할 이슈 번호만 메모하고 지금 작업 복귀', - roomName: '숲', + sceneName: '숲', capturedAt: '1시간 전', }, { id: 'thought-4', text: '리뷰 코멘트는 오늘 17시 이후에 한 번에 처리', - roomName: '벽난로', + sceneName: '벽난로', capturedAt: '어제', }, ]; diff --git a/src/entities/session/model/types.ts b/src/entities/session/model/types.ts index 16de486..0bc0939 100644 --- a/src/entities/session/model/types.ts +++ b/src/entities/session/model/types.ts @@ -36,7 +36,7 @@ export interface FocusStatCard { export interface RecentThought { id: string; text: string; - roomName: string; + sceneName: string; capturedAt: string; isCompleted?: boolean; } diff --git a/src/entities/session/model/useThoughtInbox.ts b/src/entities/session/model/useThoughtInbox.ts index e3090e1..eeb3352 100644 --- a/src/entities/session/model/useThoughtInbox.ts +++ b/src/entities/session/model/useThoughtInbox.ts @@ -24,15 +24,37 @@ const readStoredThoughts = () => { return []; } - return parsed.filter((thought): thought is RecentThought => { - return ( - thought && - typeof thought.id === 'string' && - typeof thought.text === 'string' && - typeof thought.roomName === 'string' && - typeof thought.capturedAt === 'string' && - (typeof thought.isCompleted === 'undefined' || typeof thought.isCompleted === 'boolean') - ); + return parsed.flatMap((thought): RecentThought[] => { + if (!thought || typeof thought !== 'object') { + return []; + } + + const sceneName = + typeof thought.sceneName === 'string' + ? thought.sceneName + : typeof thought.roomName === 'string' + ? thought.roomName + : null; + + if ( + typeof thought.id !== 'string' || + typeof thought.text !== 'string' || + typeof thought.capturedAt !== 'string' || + !sceneName || + (typeof thought.isCompleted !== 'undefined' && typeof thought.isCompleted !== 'boolean') + ) { + return []; + } + + return [ + { + id: thought.id, + text: thought.text, + sceneName, + capturedAt: thought.capturedAt, + isCompleted: thought.isCompleted, + }, + ]; }); } catch { return []; @@ -70,7 +92,7 @@ export const useThoughtInbox = () => { }; }, []); - const addThought = useCallback((text: string, roomName: string) => { + const addThought = useCallback((text: string, sceneName: string) => { const trimmedText = text.trim(); if (!trimmedText) { @@ -80,7 +102,7 @@ export const useThoughtInbox = () => { const thought: RecentThought = { id: `thought-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`, text: trimmedText, - roomName, + sceneName, capturedAt: '방금 전', isCompleted: false, }; diff --git a/src/features/focus-session/api/focusSessionApi.ts b/src/features/focus-session/api/focusSessionApi.ts index d31ec1f..288728b 100644 --- a/src/features/focus-session/api/focusSessionApi.ts +++ b/src/features/focus-session/api/focusSessionApi.ts @@ -6,7 +6,7 @@ export type FocusSessionCompletionType = 'goal-complete' | 'timer-complete'; export interface FocusSession { id: string; - roomId: string; + sceneId: string; goal: string; timerPresetId: string; soundPresetId: string | null; @@ -24,7 +24,7 @@ export interface FocusSession { } export interface StartFocusSessionRequest { - roomId: string; + sceneId: string; goal: string; timerPresetId: string; soundPresetId?: string | null; @@ -53,7 +53,7 @@ export const focusSessionApi = { /** * Backend Codex: * - 새로운 집중 세션을 생성하고 즉시 running 상태로 시작한다. - * - roomId, goal, timerPresetId, soundPresetId를 저장한다. + * - sceneId, goal, timerPresetId, soundPresetId를 저장한다. * - 응답에는 생성 직후의 세션 상태와 남은 시간 계산에 필요한 시각 필드를 포함한다. */ startSession: async (payload: StartFocusSessionRequest): Promise => { @@ -66,11 +66,12 @@ export const focusSessionApi = { /** * Backend Codex: * - 현재 세션의 현재 phase를 일시정지한다. + * - 클라이언트는 sessionId 대신 access token으로 사용자의 current session을 식별한다. * - phaseRemainingSeconds를 정확히 저장해 재개 시 이어서 동작하게 한다. * - 이미 paused 상태여도 멱등적으로 최신 세션 상태를 반환한다. */ - pauseSession: async (sessionId: string): Promise => { - return apiClient(`api/v1/focus-sessions/${sessionId}/pause`, { + pauseSession: async (): Promise => { + return apiClient('api/v1/focus-sessions/current/pause', { method: 'POST', }); }, @@ -78,11 +79,12 @@ export const focusSessionApi = { /** * Backend Codex: * - 일시정지된 세션을 재개하고 새 phaseEndsAt/serverNow를 반환한다. + * - 클라이언트는 sessionId 대신 access token으로 사용자의 current session을 식별한다. * - 이미 running 상태여도 멱등적으로 최신 세션 상태를 반환한다. * - 남은 시간을 다시 계산할 수 있게 phaseRemainingSeconds도 함께 내려준다. */ - resumeSession: async (sessionId: string): Promise => { - return apiClient(`api/v1/focus-sessions/${sessionId}/resume`, { + resumeSession: async (): Promise => { + return apiClient('api/v1/focus-sessions/current/resume', { method: 'POST', }); }, @@ -90,11 +92,12 @@ export const focusSessionApi = { /** * Backend Codex: * - 현재 진행 중인 phase를 처음 길이로 다시 시작한다. + * - 클라이언트는 sessionId 대신 access token으로 사용자의 current session을 식별한다. * - focus/break 어느 phase인지 유지한 채 phaseStartedAt, phaseEndsAt, phaseRemainingSeconds를 갱신한다. * - 클라이언트의 Reset 버튼은 이 API 응답으로 즉시 HUD를 다시 그린다. */ - restartCurrentPhase: async (sessionId: string): Promise => { - return apiClient(`api/v1/focus-sessions/${sessionId}/restart-phase`, { + restartCurrentPhase: async (): Promise => { + return apiClient('api/v1/focus-sessions/current/restart-phase', { method: 'POST', }); }, @@ -102,14 +105,12 @@ export const focusSessionApi = { /** * Backend Codex: * - 현재 세션을 완료 처리하고 통계 집계 대상으로 반영한다. + * - 클라이언트는 sessionId 대신 access token으로 사용자의 current session을 식별한다. * - completionType으로 goal-complete / timer-complete을 구분해 저장한다. * - 완료된 세션 스냅샷을 반환하거나, 최소한 성공적으로 완료되었음을 알 수 있는 응답을 보낸다. */ - completeSession: async ( - sessionId: string, - payload: CompleteFocusSessionRequest, - ): Promise => { - return apiClient(`api/v1/focus-sessions/${sessionId}/complete`, { + completeSession: async (payload: CompleteFocusSessionRequest): Promise => { + return apiClient('api/v1/focus-sessions/current/complete', { method: 'POST', body: JSON.stringify(payload), }); @@ -118,11 +119,12 @@ export const focusSessionApi = { /** * Backend Codex: * - 현재 세션을 중도 종료(abandon) 처리한다. + * - 클라이언트는 sessionId 대신 access token으로 사용자의 current session을 식별한다. * - 통계에서는 abandon 여부를 구분할 수 있게 저장한다. * - 성공 시 204 또는 빈 성공 응답을 반환한다. */ - abandonSession: async (sessionId: string): Promise => { - return apiClient(`api/v1/focus-sessions/${sessionId}/abandon`, { + abandonSession: async (): Promise => { + return apiClient('api/v1/focus-sessions/current/abandon', { method: 'POST', expectNoContent: true, }); diff --git a/src/features/focus-session/model/useFocusSessionEngine.ts b/src/features/focus-session/model/useFocusSessionEngine.ts index 761c64a..c803101 100644 --- a/src/features/focus-session/model/useFocusSessionEngine.ts +++ b/src/features/focus-session/model/useFocusSessionEngine.ts @@ -199,7 +199,7 @@ export const useFocusSessionEngine = (): UseFocusSessionEngineResult => { } const session = await runMutation( - () => focusSessionApi.pauseSession(currentSession.id), + () => focusSessionApi.pauseSession(), '세션을 일시정지하지 못했어요.', ); @@ -211,7 +211,7 @@ export const useFocusSessionEngine = (): UseFocusSessionEngineResult => { } const session = await runMutation( - () => focusSessionApi.resumeSession(currentSession.id), + () => focusSessionApi.resumeSession(), '세션을 다시 시작하지 못했어요.', ); @@ -223,7 +223,7 @@ export const useFocusSessionEngine = (): UseFocusSessionEngineResult => { } const session = await runMutation( - () => focusSessionApi.restartCurrentPhase(currentSession.id), + () => focusSessionApi.restartCurrentPhase(), '현재 페이즈를 다시 시작하지 못했어요.', ); @@ -235,7 +235,7 @@ export const useFocusSessionEngine = (): UseFocusSessionEngineResult => { } const session = await runMutation( - () => focusSessionApi.completeSession(currentSession.id, payload), + () => focusSessionApi.completeSession(payload), '세션을 완료 처리하지 못했어요.', ); @@ -251,7 +251,7 @@ export const useFocusSessionEngine = (): UseFocusSessionEngineResult => { } const result = await runMutation( - () => focusSessionApi.abandonSession(currentSession.id), + () => focusSessionApi.abandonSession(), '세션을 종료하지 못했어요.', ); diff --git a/src/features/inbox/ui/InboxList.tsx b/src/features/inbox/ui/InboxList.tsx index 38458f4..789acd0 100644 --- a/src/features/inbox/ui/InboxList.tsx +++ b/src/features/inbox/ui/InboxList.tsx @@ -38,7 +38,7 @@ export const InboxList = ({ thoughts, onCompleteThought, onDeleteThought, classN {thought.text}

- {thought.roomName} · {thought.capturedAt} + {thought.sceneName} · {thought.capturedAt}

); diff --git a/src/features/space-select/index.ts b/src/features/space-select/index.ts deleted file mode 100644 index 4dd528e..0000000 --- a/src/features/space-select/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ui/SpaceSelectCarousel'; diff --git a/src/shared/lib/apiClient.ts b/src/shared/lib/apiClient.ts index 5acd569..3d5ecdb 100644 --- a/src/shared/lib/apiClient.ts +++ b/src/shared/lib/apiClient.ts @@ -7,6 +7,7 @@ */ import Cookies from 'js-cookie'; +import { useAuthStore } from '@/store/useAuthStore'; const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:8080'; const TOKEN_COOKIE_KEY = 'vr_access_token'; @@ -41,6 +42,16 @@ const isRecord = (value: unknown): value is Record => { return typeof value === 'object' && value !== null; }; +const readAccessToken = () => { + const storeToken = useAuthStore.getState().accessToken; + + if (storeToken) { + return storeToken; + } + + return Cookies.get(TOKEN_COOKIE_KEY) ?? null; +}; + const readErrorMessage = async (response: Response) => { const contentType = response.headers.get('content-type') ?? ''; @@ -67,7 +78,7 @@ export const apiClient = async ( ...requestOptions } = options; const url = `${API_BASE_URL}${endpoint.startsWith('/') ? endpoint : `/${endpoint}`}`; - const token = Cookies.get(TOKEN_COOKIE_KEY); + const token = readAccessToken(); const defaultHeaders: Record = { 'Content-Type': 'application/json', }; diff --git a/src/widgets/control-center-sheet/ui/ControlCenterSheetWidget.tsx b/src/widgets/control-center-sheet/ui/ControlCenterSheetWidget.tsx index 91fca2a..df3aba9 100644 --- a/src/widgets/control-center-sheet/ui/ControlCenterSheetWidget.tsx +++ b/src/widgets/control-center-sheet/ui/ControlCenterSheetWidget.tsx @@ -5,7 +5,7 @@ import type { PlanTier } from '@/entities/plan'; import { PRO_FEATURE_CARDS, } from '@/entities/plan'; -import { getRoomCardBackgroundStyle, type RoomTheme } from '@/entities/room'; +import { getSceneCardBackgroundStyle, type SceneTheme } from '@/entities/scene'; import { SOUND_PRESETS, type TimerPreset } from '@/entities/session'; import { cn } from '@/shared/lib/cn'; import { useReducedMotion } from '@/shared/lib/useReducedMotion'; @@ -13,8 +13,8 @@ import { Toggle } from '@/shared/ui'; interface ControlCenterSheetWidgetProps { plan: PlanTier; - rooms: RoomTheme[]; - selectedRoomId: string; + scenes: SceneTheme[]; + selectedSceneId: string; selectedTimerLabel: string; selectedSoundPresetId: string; sceneRecommendedSoundLabel: string; @@ -22,7 +22,7 @@ interface ControlCenterSheetWidgetProps { timerPresets: TimerPreset[]; autoHideControls: boolean; onAutoHideControlsChange: (next: boolean) => void; - onSelectRoom: (roomId: string) => void; + onSelectScene: (sceneId: string) => void; onSelectTimer: (timerLabel: string) => void; onSelectSound: (presetId: string) => void; onSelectProFeature: (featureId: string) => void; @@ -40,8 +40,8 @@ const SectionTitle = ({ title, description }: { title: string; description: stri export const ControlCenterSheetWidget = ({ plan, - rooms, - selectedRoomId, + scenes, + selectedSceneId, selectedTimerLabel, selectedSoundPresetId, sceneRecommendedSoundLabel, @@ -49,7 +49,7 @@ export const ControlCenterSheetWidget = ({ timerPresets, autoHideControls, onAutoHideControlsChange, - onSelectRoom, + onSelectScene, onSelectTimer, onSelectSound, onSelectProFeature, @@ -64,14 +64,14 @@ export const ControlCenterSheetWidget = ({ ? 'transition-none' : 'transition-colors duration-[220ms] ease-out'; - const selectedRoom = useMemo(() => { - return rooms.find((room) => room.id === selectedRoomId) ?? rooms[0]; - }, [rooms, selectedRoomId]); + const selectedScene = useMemo(() => { + return scenes.find((scene) => scene.id === selectedSceneId) ?? scenes[0]; + }, [scenes, selectedSceneId]); return (
- +
- {rooms.slice(0, 6).map((room) => { - const selected = room.id === selectedRoomId; + {scenes.slice(0, 6).map((scene) => { + const selected = scene.id === selectedSceneId; return ( ); diff --git a/src/widgets/space-setup-drawer/ui/SpaceSetupDrawerWidget.tsx b/src/widgets/space-setup-drawer/ui/SpaceSetupDrawerWidget.tsx index c18ae4d..9e952f9 100644 --- a/src/widgets/space-setup-drawer/ui/SpaceSetupDrawerWidget.tsx +++ b/src/widgets/space-setup-drawer/ui/SpaceSetupDrawerWidget.tsx @@ -1,9 +1,9 @@ 'use client'; import { useEffect, useMemo, useRef, useState, type FormEvent } from 'react'; -import type { RoomTheme } from '@/entities/room'; +import type { SceneTheme } from '@/entities/scene'; import type { GoalChip, SoundPreset, TimerPreset } from '@/entities/session'; -import { SpaceSelectCarousel } from '@/features/space-select'; +import { SceneSelectCarousel } from '@/features/scene-select'; import { SessionGoalField } from '@/features/session-goal'; import { Button } from '@/shared/ui'; import { cn } from '@/shared/lib/cn'; @@ -12,8 +12,8 @@ type RitualPopover = 'space' | 'timer' | 'sound'; interface SpaceSetupDrawerWidgetProps { open: boolean; - rooms: RoomTheme[]; - selectedRoomId: string; + scenes: SceneTheme[]; + selectedSceneId: string; selectedTimerLabel: string; selectedSoundPresetId: string; goalInput: string; @@ -22,7 +22,7 @@ interface SpaceSetupDrawerWidgetProps { soundPresets: SoundPreset[]; timerPresets: TimerPreset[]; canStart: boolean; - onRoomSelect: (roomId: string) => void; + onSceneSelect: (sceneId: string) => void; onTimerSelect: (timerLabel: string) => void; onSoundSelect: (soundPresetId: string) => void; onGoalChange: (value: string) => void; @@ -63,8 +63,8 @@ const SummaryChip = ({ label, value, open, onClick }: SummaryChipProps) => { export const SpaceSetupDrawerWidget = ({ open, - rooms, - selectedRoomId, + scenes, + selectedSceneId, selectedTimerLabel, selectedSoundPresetId, goalInput, @@ -73,7 +73,7 @@ export const SpaceSetupDrawerWidget = ({ soundPresets, timerPresets, canStart, - onRoomSelect, + onSceneSelect, onTimerSelect, onSoundSelect, onGoalChange, @@ -84,9 +84,9 @@ export const SpaceSetupDrawerWidget = ({ const [openPopover, setOpenPopover] = useState(null); const panelRef = useRef(null); - const selectedRoom = useMemo(() => { - return rooms.find((room) => room.id === selectedRoomId) ?? rooms[0]; - }, [rooms, selectedRoomId]); + const selectedScene = useMemo(() => { + return scenes.find((scene) => scene.id === selectedSceneId) ?? scenes[0]; + }, [scenes, selectedSceneId]); const selectedSoundLabel = useMemo(() => { return ( @@ -183,8 +183,8 @@ export const SpaceSetupDrawerWidget = ({
togglePopover('space')} /> @@ -204,11 +204,11 @@ export const SpaceSetupDrawerWidget = ({ {openPopover === 'space' ? (
- { - onRoomSelect(roomId); + { + onSceneSelect(sceneId); setOpenPopover(null); }} /> diff --git a/src/widgets/space-tools-dock/ui/SpaceToolsDockWidget.tsx b/src/widgets/space-tools-dock/ui/SpaceToolsDockWidget.tsx index 04777f0..5908565 100644 --- a/src/widgets/space-tools-dock/ui/SpaceToolsDockWidget.tsx +++ b/src/widgets/space-tools-dock/ui/SpaceToolsDockWidget.tsx @@ -1,7 +1,7 @@ 'use client'; import { useEffect, useMemo, useRef, useState, type KeyboardEvent as ReactKeyboardEvent } from 'react'; import type { PlanTier } from '@/entities/plan'; -import type { RoomTheme } from '@/entities/room'; +import type { SceneTheme } from '@/entities/scene'; import { SOUND_PRESETS, type RecentThought, type TimerPreset } from '@/entities/session'; import { ExitHoldButton } from '@/features/exit-hold'; import { ManagePlanSheetContent, PaywallSheetContent } from '@/features/paywall-sheet'; @@ -19,8 +19,8 @@ import { QuickSoundPopover } from './popovers/QuickSoundPopover'; import { InboxToolPanel } from './panels/InboxToolPanel'; interface SpaceToolsDockWidgetProps { isFocusMode: boolean; - rooms: RoomTheme[]; - selectedRoomId: string; + scenes: SceneTheme[]; + selectedSceneId: string; selectedTimerLabel: string; timerPresets: TimerPreset[]; selectedPresetId: string; @@ -32,7 +32,7 @@ interface SpaceToolsDockWidgetProps { thoughtCount: number; sceneRecommendedSoundLabel: string; sceneRecommendedTimerLabel: string; - onRoomSelect: (roomId: string) => void; + onSceneSelect: (sceneId: string) => void; onTimerSelect: (timerLabel: string) => void; onQuickSoundSelect: (presetId: string) => void; onCaptureThought: (note: string) => RecentThought | null; @@ -47,8 +47,8 @@ interface SpaceToolsDockWidgetProps { export const SpaceToolsDockWidget = ({ isFocusMode, - rooms, - selectedRoomId, + scenes, + selectedSceneId, selectedTimerLabel, timerPresets, selectedPresetId, @@ -60,7 +60,7 @@ export const SpaceToolsDockWidget = ({ thoughtCount, sceneRecommendedSoundLabel, sceneRecommendedTimerLabel, - onRoomSelect, + onSceneSelect, onTimerSelect, onQuickSoundSelect, onCaptureThought, @@ -495,8 +495,8 @@ export const SpaceToolsDockWidget = ({ {utilityPanel === 'control-center' ? ( { - onRoomSelect(roomId); + onSelectScene={(sceneId) => { + onSceneSelect(sceneId); }} onSelectTimer={(label) => { onTimerSelect(label); diff --git a/src/widgets/space-tools-dock/ui/panels/SettingsToolPanel.tsx b/src/widgets/space-tools-dock/ui/panels/SettingsToolPanel.tsx index d1ec011..5fcc07c 100644 --- a/src/widgets/space-tools-dock/ui/panels/SettingsToolPanel.tsx +++ b/src/widgets/space-tools-dock/ui/panels/SettingsToolPanel.tsx @@ -1,26 +1,26 @@ 'use client'; import { useState } from 'react'; -import type { RoomTheme } from '@/entities/room'; +import type { SceneTheme } from '@/entities/scene'; import type { TimerPreset } from '@/entities/session'; import { DEFAULT_PRESET_OPTIONS } from '@/shared/config/settingsOptions'; import { cn } from '@/shared/lib/cn'; interface SettingsToolPanelProps { - rooms: RoomTheme[]; - selectedRoomId: string; + scenes: SceneTheme[]; + selectedSceneId: string; selectedTimerLabel: string; timerPresets: TimerPreset[]; - onSelectRoom: (roomId: string) => void; + onSelectScene: (sceneId: string) => void; onSelectTimer: (timerLabel: string) => void; } export const SettingsToolPanel = ({ - rooms, - selectedRoomId, + scenes, + selectedSceneId, selectedTimerLabel, timerPresets, - onSelectRoom, + onSelectScene, onSelectTimer, }: SettingsToolPanelProps) => { const [reduceMotion, setReduceMotion] = useState(false); @@ -59,17 +59,17 @@ export const SettingsToolPanel = ({
-

공간

-

몰입 중에도 공간을 바꿀 수 있어요.

+

배경

+

몰입 중에도 배경 scene을 바꿀 수 있어요.

- {rooms.slice(0, 4).map((room) => { - const selected = room.id === selectedRoomId; + {scenes.slice(0, 4).map((scene) => { + const selected = scene.id === selectedSceneId; return ( ); })} diff --git a/src/widgets/space-workspace/ui/SpaceWorkspaceWidget.tsx b/src/widgets/space-workspace/ui/SpaceWorkspaceWidget.tsx index 68de1ce..1be8d8a 100644 --- a/src/widgets/space-workspace/ui/SpaceWorkspaceWidget.tsx +++ b/src/widgets/space-workspace/ui/SpaceWorkspaceWidget.tsx @@ -3,10 +3,10 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useSearchParams } from 'next/navigation'; import { - getRoomBackgroundStyle, - getRoomById, - ROOM_THEMES, -} from '@/entities/room'; + getSceneBackgroundStyle, + getSceneById, + SCENE_THEMES, +} from '@/entities/scene'; import { GOAL_CHIPS, SOUND_PRESETS, @@ -62,16 +62,16 @@ const readStoredWorkspaceSelection = (): StoredWorkspaceSelection => { } }; -const resolveInitialRoomId = (roomIdFromQuery: string | null, storedSceneId?: string) => { - if (roomIdFromQuery && getRoomById(roomIdFromQuery)) { - return roomIdFromQuery; +const resolveInitialSceneId = (sceneIdFromQuery: string | null, storedSceneId?: string) => { + if (sceneIdFromQuery && getSceneById(sceneIdFromQuery)) { + return sceneIdFromQuery; } - if (storedSceneId && getRoomById(storedSceneId)) { + if (storedSceneId && getSceneById(storedSceneId)) { return storedSceneId; } - return ROOM_THEMES[0].id; + return SCENE_THEMES[0].id; }; const resolveInitialSoundPreset = ( @@ -150,11 +150,11 @@ const resolveFocusTimeDisplayFromTimerLabel = (timerLabel: string) => { export const SpaceWorkspaceWidget = () => { const searchParams = useSearchParams(); - const roomQuery = searchParams.get('room'); + const sceneQuery = searchParams.get('scene') ?? searchParams.get('room'); const goalQuery = searchParams.get('goal')?.trim() ?? ''; const soundQuery = searchParams.get('sound'); const timerQuery = searchParams.get('timer'); - const hasQueryOverrides = Boolean(roomQuery || goalQuery || soundQuery || timerQuery); + const hasQueryOverrides = Boolean(sceneQuery || goalQuery || soundQuery || timerQuery); const { thoughts, thoughtCount, @@ -166,22 +166,22 @@ export const SpaceWorkspaceWidget = () => { setThoughtCompleted, } = useThoughtInbox(); - const initialRoomId = resolveInitialRoomId(roomQuery, undefined); - const initialRoom = getRoomById(initialRoomId) ?? ROOM_THEMES[0]; + const initialSceneId = resolveInitialSceneId(sceneQuery, undefined); + const initialScene = getSceneById(initialSceneId) ?? SCENE_THEMES[0]; const initialGoal = goalQuery; const initialSoundPresetId = resolveInitialSoundPreset( soundQuery, undefined, - initialRoom.recommendedSoundPresetId, + initialScene.recommendedSoundPresetId, ); const initialTimerLabel = resolveInitialTimerLabel( timerQuery, undefined, - initialRoom.recommendedTimerPresetId, + initialScene.recommendedTimerPresetId, ); const [workspaceMode, setWorkspaceMode] = useState('setup'); - const [selectedRoomId, setSelectedRoomId] = useState(initialRoomId); + const [selectedSceneId, setSelectedSceneId] = useState(initialSceneId); const [selectedTimerLabel, setSelectedTimerLabel] = useState(initialTimerLabel); const [goalInput, setGoalInput] = useState(initialGoal); const [selectedGoalId, setSelectedGoalId] = useState(null); @@ -216,19 +216,19 @@ export const SpaceWorkspaceWidget = () => { abandonSession, } = useFocusSessionEngine(); - const selectedRoom = useMemo(() => { - return getRoomById(selectedRoomId) ?? ROOM_THEMES[0]; - }, [selectedRoomId]); + const selectedScene = useMemo(() => { + return getSceneById(selectedSceneId) ?? SCENE_THEMES[0]; + }, [selectedSceneId]); - const setupRooms = useMemo(() => { - const visibleRooms = ROOM_THEMES.slice(0, 6); + const setupScenes = useMemo(() => { + const visibleScenes = SCENE_THEMES.slice(0, 6); - if (visibleRooms.some((room) => room.id === selectedRoom.id)) { - return visibleRooms; + if (visibleScenes.some((scene) => scene.id === selectedScene.id)) { + return visibleScenes; } - return [selectedRoom, ...visibleRooms].slice(0, 6); - }, [selectedRoom]); + return [selectedScene, ...visibleScenes].slice(0, 6); + }, [selectedScene]); const canStart = goalInput.trim().length > 0; const isFocusMode = workspaceMode === 'focus'; @@ -237,17 +237,17 @@ export const SpaceWorkspaceWidget = () => { const resolvedTimeDisplay = timeDisplay ?? resolveFocusTimeDisplayFromTimerLabel(selectedTimerLabel); const applyRecommendedSelections = useCallback(( - roomId: string, + sceneId: string, overrideState: SelectionOverride = selectionOverride, ) => { - const room = getRoomById(roomId); + const scene = getSceneById(sceneId); - if (!room) { + if (!scene) { return; } if (!overrideState.timer) { - const recommendedTimerLabel = resolveTimerLabelFromPresetId(room.recommendedTimerPresetId); + const recommendedTimerLabel = resolveTimerLabelFromPresetId(scene.recommendedTimerPresetId); if (recommendedTimerLabel) { setSelectedTimerLabel(recommendedTimerLabel); @@ -256,9 +256,9 @@ export const SpaceWorkspaceWidget = () => { if ( !overrideState.sound && - SOUND_PRESETS.some((preset) => preset.id === room.recommendedSoundPresetId) + SOUND_PRESETS.some((preset) => preset.id === scene.recommendedSoundPresetId) ) { - setSelectedPresetId(room.recommendedSoundPresetId); + setSelectedPresetId(scene.recommendedSoundPresetId); } }, [selectionOverride, setSelectedPresetId]); @@ -268,8 +268,8 @@ export const SpaceWorkspaceWidget = () => { sound: Boolean(storedSelection.override?.sound), timer: Boolean(storedSelection.override?.timer), }; - const restoredRoomId = - !roomQuery && storedSelection.sceneId && getRoomById(storedSelection.sceneId) + const restoredSceneId = + !sceneQuery && storedSelection.sceneId && getSceneById(storedSelection.sceneId) ? storedSelection.sceneId : null; const restoredTimerLabel = !timerQuery @@ -283,8 +283,8 @@ export const SpaceWorkspaceWidget = () => { const rafId = window.requestAnimationFrame(() => { setSelectionOverride(restoredSelectionOverride); - if (restoredRoomId) { - setSelectedRoomId(restoredRoomId); + if (restoredSceneId) { + setSelectedSceneId(restoredSceneId); } if (restoredTimerLabel) { @@ -306,7 +306,7 @@ export const SpaceWorkspaceWidget = () => { return () => { window.cancelAnimationFrame(rafId); }; - }, [goalQuery, hasQueryOverrides, roomQuery, setSelectedPresetId, soundQuery, timerQuery]); + }, [goalQuery, hasQueryOverrides, sceneQuery, setSelectedPresetId, soundQuery, timerQuery]); useEffect(() => { if (!currentSession) { @@ -321,7 +321,7 @@ export const SpaceWorkspaceWidget = () => { ? currentSession.soundPresetId : selectedPresetId; const rafId = window.requestAnimationFrame(() => { - setSelectedRoomId(currentSession.roomId); + setSelectedSceneId(currentSession.sceneId); setSelectedTimerLabel(nextTimerLabel); setSelectedPresetId(nextSoundPresetId); setGoalInput(currentSession.goal); @@ -336,9 +336,9 @@ export const SpaceWorkspaceWidget = () => { }; }, [currentSession, selectedPresetId, selectedTimerLabel, setSelectedPresetId]); - const handleSelectRoom = (roomId: string) => { - setSelectedRoomId(roomId); - applyRecommendedSelections(roomId); + const handleSelectScene = (sceneId: string) => { + setSelectedSceneId(sceneId); + applyRecommendedSelections(sceneId); }; const handleSelectTimer = (timerLabel: string, markOverride = false) => { @@ -407,7 +407,7 @@ export const SpaceWorkspaceWidget = () => { setWorkspaceMode('focus'); const startedSession = await startSession({ - roomId: selectedRoomId, + sceneId: selectedSceneId, goal: trimmedGoal, timerPresetId, soundPresetId: selectedPresetId, @@ -552,21 +552,21 @@ export const SpaceWorkspaceWidget = () => { window.localStorage.setItem( WORKSPACE_SELECTION_STORAGE_KEY, JSON.stringify({ - sceneId: selectedRoomId, + sceneId: selectedSceneId, timerPresetId, soundPresetId: selectedPresetId, goal: normalizedGoal, override: selectionOverride, }), ); - }, [goalInput, hasHydratedSelection, resumeGoal, selectedRoomId, selectedTimerLabel, selectedPresetId, selectionOverride, showResumePrompt]); + }, [goalInput, hasHydratedSelection, resumeGoal, selectedSceneId, selectedTimerLabel, selectedPresetId, selectionOverride, showResumePrompt]); return (
@@ -575,8 +575,8 @@ export const SpaceWorkspaceWidget = () => { { soundPresets={SOUND_PRESETS} timerPresets={TIMER_SELECTION_PRESETS} canStart={canStart} - onRoomSelect={handleSelectRoom} + onSceneSelect={handleSelectScene} onTimerSelect={(timerLabel) => handleSelectTimer(timerLabel, true)} onSoundSelect={(presetId) => handleSelectSound(presetId, true)} onGoalChange={handleGoalChange} @@ -641,23 +641,23 @@ export const SpaceWorkspaceWidget = () => { handleSelectTimer(timerLabel, true)} onQuickSoundSelect={(presetId) => handleSelectSound(presetId, true)} - sceneRecommendedSoundLabel={selectedRoom.recommendedSound} - sceneRecommendedTimerLabel={resolveTimerLabelFromPresetId(selectedRoom.recommendedTimerPresetId) ?? selectedTimerLabel} + sceneRecommendedSoundLabel={selectedScene.recommendedSound} + sceneRecommendedTimerLabel={resolveTimerLabelFromPresetId(selectedScene.recommendedTimerPresetId) ?? selectedTimerLabel} soundVolume={masterVolume} onSetSoundVolume={setMasterVolume} isSoundMuted={isMuted} onSetSoundMuted={setMuted} - onCaptureThought={(note) => addThought(note, selectedRoom.name)} + onCaptureThought={(note) => addThought(note, selectedScene.name)} onDeleteThought={removeThought} onSetThoughtCompleted={setThoughtCompleted} onRestoreThought={restoreThought}