From f4910238a009d30e0cc7941721593ee51496bcdd Mon Sep 17 00:00:00 2001 From: corpi Date: Mon, 16 Mar 2026 16:21:12 +0900 Subject: [PATCH] =?UTF-8?q?chore(web):=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20legacy=20=EC=9C=84=EC=A0=AF=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/widgets/control-center-sheet/index.ts | 1 - .../ui/ControlCenterSheetWidget.tsx | 245 --------------- .../ui/FocusPlanManageSheet.tsx | 155 ---------- .../space-focus-hud/ui/ReturnPrompt.tsx | 199 ------------- src/widgets/space-sheet-shell/index.ts | 1 - .../space-sheet-shell/ui/SpaceSideSheet.tsx | 167 ----------- src/widgets/space-timer-hud/index.ts | 1 - .../ui/SpaceTimerHudWidget.tsx | 128 -------- src/widgets/space-tools-dock/index.ts | 2 - .../model/getQuickSoundPresets.ts | 19 -- src/widgets/space-tools-dock/model/types.ts | 2 - .../model/useSpaceToolsDockHandlers.ts | 206 ------------- .../model/useSpaceToolsDockState.ts | 143 --------- .../space-tools-dock/ui/FocusModeAnchors.tsx | 97 ------ .../space-tools-dock/ui/FocusRightRail.tsx | 192 ------------ .../ui/SpaceToolsDockWidget.tsx | 278 ------------------ src/widgets/space-tools-dock/ui/constants.tsx | 86 ------ .../ui/panels/InboxToolPanel.tsx | 68 ----- .../ui/panels/SettingsToolPanel.tsx | 137 --------- .../ui/panels/StatsToolPanel.tsx | 27 -- .../ui/popovers/QuickNotesPopover.tsx | 51 ---- .../ui/popovers/QuickSoundPopover.tsx | 101 ------- .../model/useAwayReturnRecovery.ts | 184 ------------ 23 files changed, 2490 deletions(-) delete mode 100644 src/widgets/control-center-sheet/index.ts delete mode 100644 src/widgets/control-center-sheet/ui/ControlCenterSheetWidget.tsx delete mode 100644 src/widgets/focus-dashboard/ui/FocusPlanManageSheet.tsx delete mode 100644 src/widgets/space-focus-hud/ui/ReturnPrompt.tsx delete mode 100644 src/widgets/space-sheet-shell/index.ts delete mode 100644 src/widgets/space-sheet-shell/ui/SpaceSideSheet.tsx delete mode 100644 src/widgets/space-timer-hud/index.ts delete mode 100644 src/widgets/space-timer-hud/ui/SpaceTimerHudWidget.tsx delete mode 100644 src/widgets/space-tools-dock/index.ts delete mode 100644 src/widgets/space-tools-dock/model/getQuickSoundPresets.ts delete mode 100644 src/widgets/space-tools-dock/model/types.ts delete mode 100644 src/widgets/space-tools-dock/model/useSpaceToolsDockHandlers.ts delete mode 100644 src/widgets/space-tools-dock/model/useSpaceToolsDockState.ts delete mode 100644 src/widgets/space-tools-dock/ui/FocusModeAnchors.tsx delete mode 100644 src/widgets/space-tools-dock/ui/FocusRightRail.tsx delete mode 100644 src/widgets/space-tools-dock/ui/SpaceToolsDockWidget.tsx delete mode 100644 src/widgets/space-tools-dock/ui/constants.tsx delete mode 100644 src/widgets/space-tools-dock/ui/panels/InboxToolPanel.tsx delete mode 100644 src/widgets/space-tools-dock/ui/panels/SettingsToolPanel.tsx delete mode 100644 src/widgets/space-tools-dock/ui/panels/StatsToolPanel.tsx delete mode 100644 src/widgets/space-tools-dock/ui/popovers/QuickNotesPopover.tsx delete mode 100644 src/widgets/space-tools-dock/ui/popovers/QuickSoundPopover.tsx delete mode 100644 src/widgets/space-workspace/model/useAwayReturnRecovery.ts diff --git a/src/widgets/control-center-sheet/index.ts b/src/widgets/control-center-sheet/index.ts deleted file mode 100644 index feb4811..0000000 --- a/src/widgets/control-center-sheet/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ui/ControlCenterSheetWidget'; diff --git a/src/widgets/control-center-sheet/ui/ControlCenterSheetWidget.tsx b/src/widgets/control-center-sheet/ui/ControlCenterSheetWidget.tsx deleted file mode 100644 index e48ee88..0000000 --- a/src/widgets/control-center-sheet/ui/ControlCenterSheetWidget.tsx +++ /dev/null @@ -1,245 +0,0 @@ -'use client'; - -import { useMemo } from 'react'; -import type { PlanTier } from '@/entities/plan'; -import { - PRO_FEATURE_CARDS, -} from '@/entities/plan'; -import type { SceneTheme } from '@/entities/scene'; -import { getSceneCardBackgroundStyle, type SceneAssetMap } from '@/entities/media'; -import { SOUND_PRESETS, type TimerPreset } from '@/entities/session'; -import { copy } from '@/shared/i18n'; -import { cn } from '@/shared/lib/cn'; -import { useReducedMotion } from '@/shared/lib/useReducedMotion'; -import { useDragScroll } from '@/shared/lib/useDragScroll'; -import { Toggle } from '@/shared/ui'; - -interface ControlCenterSheetWidgetProps { - plan: PlanTier; - scenes: SceneTheme[]; - sceneAssetMap?: SceneAssetMap; - selectedSceneId: string; - selectedTimerLabel: string; - selectedSoundPresetId: string; - sceneRecommendedSoundLabel: string; - sceneRecommendedTimerLabel: string; - timerPresets: TimerPreset[]; - autoHideControls: boolean; - onAutoHideControlsChange: (next: boolean) => void; - onSelectScene: (sceneId: string) => void; - onSelectTimer: (timerLabel: string) => void; - onSelectSound: (presetId: string) => void; - onSelectProFeature: (featureId: string) => void; - onLockedClick: (source: string) => void; -} - -const SectionTitle = ({ title, description }: { title: string; description: string }) => { - return ( -
-

{title}

-

{description}

-
- ); -}; - -export const ControlCenterSheetWidget = ({ - plan, - scenes, - sceneAssetMap, - selectedSceneId, - selectedTimerLabel, - selectedSoundPresetId, - sceneRecommendedSoundLabel, - sceneRecommendedTimerLabel, - timerPresets, - autoHideControls, - onAutoHideControlsChange, - onSelectScene, - onSelectTimer, - onSelectSound, - onSelectProFeature, - onLockedClick, -}: ControlCenterSheetWidgetProps) => { - const { controlCenter } = copy.space; - const reducedMotion = useReducedMotion(); - const isPro = plan === 'pro'; - const interactiveMotionClass = reducedMotion - ? 'transition-none' - : 'transition-[transform,background-color,border-color,box-shadow,color,opacity] duration-[220ms] ease-out'; - const colorMotionClass = reducedMotion - ? 'transition-none' - : 'transition-colors duration-[220ms] ease-out'; - - const selectedScene = useMemo(() => { - return scenes.find((scene) => scene.id === selectedSceneId) ?? scenes[0]; - }, [scenes, selectedSceneId]); - - const { - containerRef: sceneContainerRef, - events: sceneDragEvents, - isDragging: isSceneDragging, - shouldSuppressClick: shouldSuppressSceneClick, - } = useDragScroll(); - - return ( -
-
- -
- {scenes.slice(0, 6).map((scene) => { - const selected = scene.id === selectedSceneId; - - return ( - - ); - })} -
-
- -
- -
- {timerPresets.slice(0, 3).map((preset) => { - const selected = preset.label === selectedTimerLabel; - - return ( - - ); - })} -
-
- -
- preset.id === selectedSoundPresetId)?.label ?? copy.common.default} - /> -
- {SOUND_PRESETS.slice(0, 6).map((preset) => { - const selected = preset.id === selectedSoundPresetId; - - return ( - - ); - })} -
-
- -
-

{controlCenter.recommendation(sceneRecommendedSoundLabel, sceneRecommendedTimerLabel)}

-

{controlCenter.recommendationHint}

-
- -
- -
- {PRO_FEATURE_CARDS.map((feature) => { - const locked = !isPro; - - return ( - - ); - })} -
-
- -
-
-
-

{controlCenter.autoHideTitle}

-

{controlCenter.autoHideDescription}

-
- -
-
-
- ); -}; diff --git a/src/widgets/focus-dashboard/ui/FocusPlanManageSheet.tsx b/src/widgets/focus-dashboard/ui/FocusPlanManageSheet.tsx deleted file mode 100644 index 09dc359..0000000 --- a/src/widgets/focus-dashboard/ui/FocusPlanManageSheet.tsx +++ /dev/null @@ -1,155 +0,0 @@ -'use client'; - -import type { RefObject } from 'react'; -import type { FocusPlanItem } from '@/entities/focus-plan'; -import { cn } from '@/shared/lib/cn'; -import { Modal } from '@/shared/ui'; -import { FocusPlanListRow } from './FocusPlanListRow'; - -export type FocusPlanEditingState = - | { - mode: 'new'; - value: string; - } - | { - mode: 'edit'; - itemId: string; - value: string; - } - | null; - -interface FocusPlanManageSheetProps { - isOpen: boolean; - planItems: FocusPlanItem[]; - selectedPlanItemId: string | null; - editingState: FocusPlanEditingState; - isSaving: boolean; - canAddMore: boolean; - isPro: boolean; - inputRef?: RefObject; - onClose: () => void; - onAddBlock: () => void; - onDraftChange: (value: string) => void; - onSelect: (item: FocusPlanItem) => void; - onEdit: (item: FocusPlanItem) => void; - onDelete: (itemId: string) => void; - onSave: () => void; - onCancel: () => void; -} - -const manageSheetCopy = { - title: '블둝 정리', - description: '집쀑에 λ“€μ–΄κ°„ λ’€ μ΄μ–΄κ°ˆ λΈ”λ‘λ§Œ κ°€λ³κ²Œ λ‚¨κ²¨λ‘μ„Έμš”.', - empty: '아직 μ €μž₯된 블둝이 μ—†μ–΄μš”.', - addBlock: '+ 블둝 μΆ”κ°€', - placeholder: '예: 리뷰 μ½”λ©˜νŠΈ 2개 정리', - freeUpgradeLabel: '두 번째 λΈ”λ‘λΆ€ν„°λŠ” PRO', - maxBlocksReached: 'μ΅œλŒ€ 5κ°œκΉŒμ§€ 정리해 λ‘˜ 수 μžˆμ–΄μš”.', -}; - -export const FocusPlanManageSheet = ({ - isOpen, - planItems, - selectedPlanItemId, - editingState, - isSaving, - canAddMore, - isPro, - inputRef, - onClose, - onAddBlock, - onDraftChange, - onSelect, - onEdit, - onDelete, - onSave, - onCancel, -}: FocusPlanManageSheetProps) => { - const hasPendingEdit = editingState !== null; - - return ( - -
-
-
- {planItems.length === 0 && editingState?.mode !== 'new' ? ( -
- {manageSheetCopy.empty} -
- ) : null} - - {planItems.map((item, index) => ( - onSelect(item)} - onEdit={() => onEdit(item)} - onDelete={() => onDelete(item.id)} - onSave={onSave} - onCancel={onCancel} - /> - ))} - - {editingState?.mode === 'new' ? ( - {}} - onEdit={() => {}} - onDelete={() => {}} - onSave={onSave} - onCancel={onCancel} - /> - ) : null} -
-
- -
- {isPro && !canAddMore ? ( -

{manageSheetCopy.maxBlocksReached}

- ) : ( - - )} -
-
-
- ); -}; diff --git a/src/widgets/space-focus-hud/ui/ReturnPrompt.tsx b/src/widgets/space-focus-hud/ui/ReturnPrompt.tsx deleted file mode 100644 index 8a2009b..0000000 --- a/src/widgets/space-focus-hud/ui/ReturnPrompt.tsx +++ /dev/null @@ -1,199 +0,0 @@ -'use client'; - -import { copy } from '@/shared/i18n'; -import { cn } from '@/shared/lib/cn'; -import { - HUD_OPTION_CHEVRON, - HUD_OPTION_ROW, - HUD_OPTION_ROW_BREAK, - HUD_OPTION_ROW_PRIMARY, - HUD_REVEAL_BASE, - HUD_REVEAL_HIDDEN, - HUD_REVEAL_RETURN_BREAK, - HUD_REVEAL_RETURN_FOCUS, - HUD_RETURN_BODY, - HUD_RETURN_TITLE, - HUD_TRAY_CONTENT, - HUD_TRAY_HAIRLINE_BREAK, - HUD_TRAY_HAIRLINE, - HUD_TRAY_LAYER_BREAK, - HUD_TRAY_LAYER, - HUD_TRAY_SHELL_BREAK, - HUD_TRAY_SHELL, -} from './overlayStyles'; - -interface ReturnPromptProps { - open: boolean; - mode: 'focus' | 'break'; - isBusy: boolean; - onContinue: () => void; - onRefocus: () => void; - onRest?: () => void; - onNextGoal?: () => void; - onFinish: () => void; -} - -export const ReturnPrompt = ({ - open, - mode, - isBusy, - onContinue, - onRefocus, - onRest, - onNextGoal, - onFinish, -}: ReturnPromptProps) => { - const isBreakReturn = mode === 'break'; - - return ( -
-
-
-
- -
-

- {copy.space.focusHud.returnPromptEyebrow} -

-

- {isBreakReturn - ? copy.space.focusHud.returnPromptBreakTitle - : copy.space.focusHud.returnPromptFocusTitle} -

-

- {isBreakReturn - ? copy.space.focusHud.returnPromptBreakDescription - : copy.space.focusHud.returnPromptFocusDescription} -

- -
- {isBreakReturn ? ( - <> - - - - - ) : ( - <> - - - - )} -
- -
- -

- {copy.space.focusHud.returnPromptFinishHint} -

-
-
-
-
- ); -}; diff --git a/src/widgets/space-sheet-shell/index.ts b/src/widgets/space-sheet-shell/index.ts deleted file mode 100644 index 8a4fb12..0000000 --- a/src/widgets/space-sheet-shell/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ui/SpaceSideSheet'; diff --git a/src/widgets/space-sheet-shell/ui/SpaceSideSheet.tsx b/src/widgets/space-sheet-shell/ui/SpaceSideSheet.tsx deleted file mode 100644 index 3f3890c..0000000 --- a/src/widgets/space-sheet-shell/ui/SpaceSideSheet.tsx +++ /dev/null @@ -1,167 +0,0 @@ -'use client'; - -import { useEffect, useRef, useState, type ReactNode } from 'react'; -import { copy } from '@/shared/i18n'; -import { cn } from '@/shared/lib/cn'; -import { useReducedMotion } from '@/shared/lib/useReducedMotion'; - -interface SpaceSideSheetProps { - open: boolean; - title: string; - subtitle?: string; - onClose: () => void; - children: ReactNode; - footer?: ReactNode; - widthClassName?: string; - dismissible?: boolean; - headerAction?: ReactNode; - overlayClassName?: string; -} - -export const SpaceSideSheet = ({ - open, - title, - subtitle, - onClose, - children, - footer, - widthClassName, - dismissible = true, - headerAction, - overlayClassName, -}: SpaceSideSheetProps) => { - const reducedMotion = useReducedMotion(); - const transitionMs = reducedMotion ? 0 : 260; - const closeTimerRef = useRef(null); - const [shouldRender, setShouldRender] = useState(open); - const [visible, setVisible] = useState(open); - - useEffect(() => { - if (closeTimerRef.current) { - window.clearTimeout(closeTimerRef.current); - closeTimerRef.current = null; - } - - if (open) { - let nestedRaf = 0; - const raf = window.requestAnimationFrame(() => { - setShouldRender(true); - nestedRaf = window.requestAnimationFrame(() => { - setVisible(true); - }); - }); - - return () => { - window.cancelAnimationFrame(raf); - window.cancelAnimationFrame(nestedRaf); - }; - } - - const hideRaf = window.requestAnimationFrame(() => { - setVisible(false); - }); - closeTimerRef.current = window.setTimeout(() => { - setShouldRender(false); - closeTimerRef.current = null; - }, transitionMs); - - return () => { - window.cancelAnimationFrame(hideRaf); - if (closeTimerRef.current) { - window.clearTimeout(closeTimerRef.current); - closeTimerRef.current = null; - } - }; - }, [open, transitionMs]); - - useEffect(() => { - if (!open) { - return; - } - - const handleEscape = (event: KeyboardEvent) => { - if (event.key === 'Escape') { - onClose(); - } - }; - - document.addEventListener('keydown', handleEscape); - - return () => { - document.removeEventListener('keydown', handleEscape); - }; - }, [open, onClose]); - - if (!shouldRender) { - return null; - } - - return ( - <> - {dismissible ? ( - - ) : null} - - - -
{children}
- - {footer ?
{footer}
: null} - - - - ); -}; diff --git a/src/widgets/space-timer-hud/index.ts b/src/widgets/space-timer-hud/index.ts deleted file mode 100644 index baf6f7c..0000000 --- a/src/widgets/space-timer-hud/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ui/SpaceTimerHudWidget'; diff --git a/src/widgets/space-timer-hud/ui/SpaceTimerHudWidget.tsx b/src/widgets/space-timer-hud/ui/SpaceTimerHudWidget.tsx deleted file mode 100644 index c08c383..0000000 --- a/src/widgets/space-timer-hud/ui/SpaceTimerHudWidget.tsx +++ /dev/null @@ -1,128 +0,0 @@ -'use client'; - -import { copy } from '@/shared/i18n'; -import { cn } from '@/shared/lib/cn'; -import { - RECOVERY_30S_MODE_LABEL, - Restart30sAction, - useRestart30s, -} from '@/features/restart-30s'; - -interface SpaceTimerHudWidgetProps { - className?: string; - hasActiveSession?: boolean; - sessionPhase?: 'focus' | 'break' | null; - playbackState?: 'running' | 'paused' | null; - isControlsDisabled?: boolean; - canStart?: boolean; - canPause?: boolean; - canReset?: boolean; - onStartClick?: () => void; - onPauseClick?: () => void; - onResetClick?: () => void; -} - -const HUD_ACTIONS = copy.space.timerHud.actions; - -export const SpaceTimerHudWidget = ({ - className, - hasActiveSession = false, - sessionPhase = 'focus', - playbackState = 'paused', - isControlsDisabled = false, - canStart = true, - canPause = false, - canReset = false, - onStartClick, - onPauseClick, - onResetClick, -}: SpaceTimerHudWidgetProps) => { - const { isBreatheMode, triggerRestart } = useRestart30s(); - const isBreakPhase = hasActiveSession && sessionPhase === 'break'; - const modeLabel = isBreatheMode - ? RECOVERY_30S_MODE_LABEL - : !hasActiveSession - ? copy.space.timerHud.readyMode - : sessionPhase === 'break' - ? copy.space.timerHud.breakMode - : copy.space.timerHud.focusMode; - - return ( -
-
-
-
- - {modeLabel} - -
- -
- {HUD_ACTIONS.map((action) => { - const isStartAction = action.id === 'start'; - const isPauseAction = action.id === 'pause'; - const isResetAction = action.id === 'reset'; - const isDisabled = - isControlsDisabled || - (isStartAction ? !canStart : isPauseAction ? !canPause : !canReset); - const isHighlighted = - (isStartAction && playbackState !== 'running') || - (isPauseAction && playbackState === 'running'); - - return ( - - ); - })} - -
-
-
-
- ); -}; diff --git a/src/widgets/space-tools-dock/index.ts b/src/widgets/space-tools-dock/index.ts deleted file mode 100644 index 491f34b..0000000 --- a/src/widgets/space-tools-dock/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './model/types'; -export * from './ui/SpaceToolsDockWidget'; diff --git a/src/widgets/space-tools-dock/model/getQuickSoundPresets.ts b/src/widgets/space-tools-dock/model/getQuickSoundPresets.ts deleted file mode 100644 index 1a59219..0000000 --- a/src/widgets/space-tools-dock/model/getQuickSoundPresets.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { PRO_LOCKED_SOUND_IDS } from '@/entities/plan'; -import type { SoundPreset } from '@/entities/session'; - -const QUICK_SOUND_FALLBACK_IDS = ['rain-focus', 'deep-white', 'silent'] as const; - -const isQuickAvailablePreset = (preset: SoundPreset | undefined): preset is SoundPreset => { - if (!preset) { - return false; - } - - return !PRO_LOCKED_SOUND_IDS.includes(preset.id); -}; - -export const getQuickSoundPresets = (presets: SoundPreset[]) => { - return QUICK_SOUND_FALLBACK_IDS - .map((presetId) => presets.find((preset) => preset.id === presetId)) - .filter(isQuickAvailablePreset) - .slice(0, 3); -}; diff --git a/src/widgets/space-tools-dock/model/types.ts b/src/widgets/space-tools-dock/model/types.ts deleted file mode 100644 index 1afbd32..0000000 --- a/src/widgets/space-tools-dock/model/types.ts +++ /dev/null @@ -1,2 +0,0 @@ -export type SpaceAnchorPopoverId = 'sound' | 'notes'; -export type SpaceUtilityPanelId = 'control-center' | 'inbox' | 'manage-plan' | 'paywall'; diff --git a/src/widgets/space-tools-dock/model/useSpaceToolsDockHandlers.ts b/src/widgets/space-tools-dock/model/useSpaceToolsDockHandlers.ts deleted file mode 100644 index df4dd77..0000000 --- a/src/widgets/space-tools-dock/model/useSpaceToolsDockHandlers.ts +++ /dev/null @@ -1,206 +0,0 @@ -'use client'; - -import { useCallback, useState, type KeyboardEvent as ReactKeyboardEvent } from 'react'; -import { usePlanTier } from '@/entities/plan'; -import type { RecentThought } from '@/entities/session'; -import { copy } from '@/shared/i18n'; -import type { HudStatusLinePayload } from '@/shared/lib/useHudStatusLine'; -import type { SpaceAnchorPopoverId, SpaceUtilityPanelId } from './types'; - -interface UseSpaceToolsDockHandlersParams { - setIdle: (idle: boolean) => void; - setOpenPopover: (popover: SpaceAnchorPopoverId | null) => void; - setUtilityPanel: (panel: SpaceUtilityPanelId | null) => void; - onCaptureThought: (note: string) => RecentThought | null; - onDeleteThought: (thoughtId: string) => RecentThought | null; - onSetThoughtCompleted: (thoughtId: string, isCompleted: boolean) => RecentThought | null; - onRestoreThought: (thought: RecentThought) => void; - onRestoreThoughts: (thoughts: RecentThought[]) => void; - onClearInbox: () => RecentThought[]; - onStatusMessage: (payload: HudStatusLinePayload) => void; - onSetSoundVolume: (volume: number) => void; - onSetSoundMuted: (muted: boolean) => void; - soundVolume: number; - isSoundMuted: boolean; - showVolumeFeedback: (volume: number) => void; -} - -export const useSpaceToolsDockHandlers = ({ - setIdle, - setOpenPopover, - setUtilityPanel, - onCaptureThought, - onDeleteThought, - onSetThoughtCompleted, - onRestoreThought, - onRestoreThoughts, - onClearInbox, - onStatusMessage, - onSetSoundVolume, - onSetSoundMuted, - soundVolume, - isSoundMuted, - showVolumeFeedback, -}: UseSpaceToolsDockHandlersParams) => { - const { toolsDock } = copy.space; - const [noteDraft, setNoteDraft] = useState(''); - const { plan, setPlan } = usePlanTier(); - - const openUtilityPanel = useCallback((panel: SpaceUtilityPanelId) => { - setIdle(false); - setOpenPopover(null); - setUtilityPanel(panel); - }, [setIdle, setOpenPopover, setUtilityPanel]); - - const handleNoteSubmit = useCallback(() => { - const trimmedNote = noteDraft.trim(); - - if (!trimmedNote) { - return; - } - - const addedThought = onCaptureThought(trimmedNote); - - if (!addedThought) { - return; - } - - setNoteDraft(''); - onStatusMessage({ - message: toolsDock.inboxSaved, - durationMs: 4200, - priority: 'undo', - action: { - label: toolsDock.undo, - onClick: () => { - const removed = onDeleteThought(addedThought.id); - - if (!removed) { - return; - } - - onStatusMessage({ message: toolsDock.inboxSaveUndone }); - }, - }, - }); - }, [noteDraft, onCaptureThought, onDeleteThought, onStatusMessage, toolsDock.inboxSaved, toolsDock.inboxSaveUndone, toolsDock.undo]); - - const handleInboxComplete = useCallback((thought: RecentThought) => { - onSetThoughtCompleted(thought.id, !thought.isCompleted); - }, [onSetThoughtCompleted]); - - const handleInboxDelete = useCallback((thought: RecentThought) => { - const removedThought = onDeleteThought(thought.id); - - if (!removedThought) { - return; - } - - onStatusMessage({ - message: toolsDock.deleted, - durationMs: 4200, - priority: 'undo', - action: { - label: toolsDock.undo, - onClick: () => { - onRestoreThought(removedThought); - onStatusMessage({ message: toolsDock.deleteUndone }); - }, - }, - }); - }, [onDeleteThought, onRestoreThought, onStatusMessage, toolsDock.deleted, toolsDock.deleteUndone, toolsDock.undo]); - - const handleInboxClear = useCallback(() => { - const snapshot = onClearInbox(); - - if (snapshot.length === 0) { - onStatusMessage({ message: toolsDock.emptyToClear }); - return; - } - - onStatusMessage({ - message: toolsDock.clearedAll, - durationMs: 4200, - priority: 'undo', - action: { - label: toolsDock.undo, - onClick: () => { - onRestoreThoughts(snapshot); - onStatusMessage({ message: toolsDock.restored }); - }, - }, - }); - }, [onClearInbox, onRestoreThoughts, onStatusMessage, toolsDock.clearedAll, toolsDock.emptyToClear, toolsDock.restored, toolsDock.undo]); - - const handlePlanPillClick = useCallback(() => { - if (plan === 'pro') { - openUtilityPanel('manage-plan'); - return; - } - - onStatusMessage({ message: toolsDock.normalPlanInfo }); - }, [openUtilityPanel, onStatusMessage, plan, toolsDock.normalPlanInfo]); - - const handleLockedClick = useCallback((source: string) => { - onStatusMessage({ message: toolsDock.proFeatureLocked(source) }); - openUtilityPanel('paywall'); - }, [onStatusMessage, openUtilityPanel, toolsDock]); - - const handleSelectProFeature = useCallback((featureId: string) => { - const label = - featureId === 'daily-plan' - ? toolsDock.featureLabels.dailyPlan - : featureId === 'rituals' - ? toolsDock.featureLabels.rituals - : toolsDock.featureLabels.weeklyReview; - - onStatusMessage({ message: toolsDock.proFeaturePending(label) }); - }, [onStatusMessage, toolsDock]); - - const handleStartPro = useCallback(() => { - setPlan('pro'); - onStatusMessage({ message: toolsDock.purchaseMock }); - openUtilityPanel('control-center'); - }, [onStatusMessage, openUtilityPanel, setPlan, toolsDock.purchaseMock]); - - const handleVolumeChange = useCallback((nextVolume: number) => { - const clamped = Math.min(100, Math.max(0, nextVolume)); - onSetSoundVolume(clamped); - - if (isSoundMuted && clamped > 0) { - onSetSoundMuted(false); - } - - showVolumeFeedback(clamped); - }, [isSoundMuted, onSetSoundMuted, onSetSoundVolume, showVolumeFeedback]); - - const handleVolumeKeyDown = useCallback((event: ReactKeyboardEvent) => { - if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') { - return; - } - - event.preventDefault(); - - const step = event.shiftKey ? 10 : 5; - const delta = event.key === 'ArrowRight' ? step : -step; - handleVolumeChange(soundVolume + delta); - }, [handleVolumeChange, soundVolume]); - - return { - noteDraft, - setNoteDraft, - plan, - setPlan, - openUtilityPanel, - handleNoteSubmit, - handleInboxComplete, - handleInboxDelete, - handleInboxClear, - handlePlanPillClick, - handleLockedClick, - handleSelectProFeature, - handleStartPro, - handleVolumeChange, - handleVolumeKeyDown, - }; -}; diff --git a/src/widgets/space-tools-dock/model/useSpaceToolsDockState.ts b/src/widgets/space-tools-dock/model/useSpaceToolsDockState.ts deleted file mode 100644 index bd3bd7d..0000000 --- a/src/widgets/space-tools-dock/model/useSpaceToolsDockState.ts +++ /dev/null @@ -1,143 +0,0 @@ -'use client'; - -import { useEffect, useRef, useState } from 'react'; -import type { SpaceAnchorPopoverId, SpaceUtilityPanelId } from './types'; - -interface UseSpaceToolsDockStateParams { - isFocusMode: boolean; -} - -export const useSpaceToolsDockState = ({ isFocusMode }: UseSpaceToolsDockStateParams) => { - const [openPopover, setOpenPopover] = useState(null); - const [utilityPanel, setUtilityPanel] = useState(null); - const [autoHideControls, setAutoHideControls] = useState(true); - const [isIdle, setIdle] = useState(false); - const [volumeFeedback, setVolumeFeedback] = useState(null); - const volumeFeedbackTimerRef = useRef(null); - - useEffect(() => { - return () => { - if (volumeFeedbackTimerRef.current) { - window.clearTimeout(volumeFeedbackTimerRef.current); - volumeFeedbackTimerRef.current = null; - } - }; - }, []); - - useEffect(() => { - if (isFocusMode) { - return; - } - - const rafId = window.requestAnimationFrame(() => { - setOpenPopover(null); - setUtilityPanel(null); - setIdle(false); - }); - - return () => { - window.cancelAnimationFrame(rafId); - }; - }, [isFocusMode]); - - useEffect(() => { - if (!isFocusMode || openPopover || utilityPanel) { - return; - } - - let timerId: number | null = null; - - const armIdleTimer = () => { - if (timerId) { - window.clearTimeout(timerId); - } - - timerId = window.setTimeout(() => { - setIdle(true); - }, 3500); - }; - - const wake = () => { - setIdle(false); - armIdleTimer(); - }; - - armIdleTimer(); - - window.addEventListener('pointermove', wake); - window.addEventListener('keydown', wake); - window.addEventListener('pointerdown', wake); - - return () => { - if (timerId) { - window.clearTimeout(timerId); - } - window.removeEventListener('pointermove', wake); - window.removeEventListener('keydown', wake); - window.removeEventListener('pointerdown', wake); - }; - }, [isFocusMode, openPopover, utilityPanel]); - - useEffect(() => { - if (utilityPanel !== 'control-center' || !autoHideControls) { - return; - } - - let timerId: number | null = null; - const closeDelayMs = 8000; - - const armCloseTimer = () => { - if (timerId) { - window.clearTimeout(timerId); - } - - timerId = window.setTimeout(() => { - setUtilityPanel((current) => (current === 'control-center' ? null : current)); - }, closeDelayMs); - }; - - const resetTimer = () => { - armCloseTimer(); - }; - - armCloseTimer(); - window.addEventListener('pointermove', resetTimer); - window.addEventListener('pointerdown', resetTimer); - window.addEventListener('keydown', resetTimer); - - return () => { - if (timerId) { - window.clearTimeout(timerId); - } - window.removeEventListener('pointermove', resetTimer); - window.removeEventListener('pointerdown', resetTimer); - window.removeEventListener('keydown', resetTimer); - }; - }, [autoHideControls, utilityPanel]); - - const showVolumeFeedback = (nextVolume: number) => { - setVolumeFeedback(`${nextVolume}%`); - - if (volumeFeedbackTimerRef.current) { - window.clearTimeout(volumeFeedbackTimerRef.current); - } - - volumeFeedbackTimerRef.current = window.setTimeout(() => { - setVolumeFeedback(null); - volumeFeedbackTimerRef.current = null; - }, 900); - }; - - return { - openPopover, - setOpenPopover, - utilityPanel, - setUtilityPanel, - autoHideControls, - setAutoHideControls, - isIdle, - setIdle, - volumeFeedback, - showVolumeFeedback, - }; -}; diff --git a/src/widgets/space-tools-dock/ui/FocusModeAnchors.tsx b/src/widgets/space-tools-dock/ui/FocusModeAnchors.tsx deleted file mode 100644 index 09ebdbe..0000000 --- a/src/widgets/space-tools-dock/ui/FocusModeAnchors.tsx +++ /dev/null @@ -1,97 +0,0 @@ -'use client'; - -import type { KeyboardEvent as ReactKeyboardEvent } from 'react'; -import type { SoundPreset } from '@/entities/session'; -import { copy } from '@/shared/i18n'; -import type { SpaceAnchorPopoverId } from '../model/types'; -import { FocusRightRail } from './FocusRightRail'; - -interface FocusModeAnchorsProps { - isFocusMode: boolean; - isIdle: boolean; - openPopover: SpaceAnchorPopoverId | null; - thoughtCount: number; - noteDraft: string; - selectedSoundLabel: string; - isSoundMuted: boolean; - soundVolume: number; - volumeFeedback: string | null; - quickSoundPresets: SoundPreset[]; - selectedPresetId: string; - onClosePopover: () => void; - onOpenInbox: () => void; - onOpenControlCenter: () => void; - onToggleNotes: () => void; - onToggleSound: () => void; - onNoteDraftChange: (value: string) => void; - onNoteSubmit: () => void; - onToggleMute: () => void; - onVolumeChange: (nextVolume: number) => void; - onVolumeKeyDown: (event: ReactKeyboardEvent) => void; - onSelectPreset: (presetId: string) => void; -} - -export const FocusModeAnchors = ({ - isFocusMode, - isIdle, - openPopover, - thoughtCount, - noteDraft, - selectedSoundLabel, - isSoundMuted, - soundVolume, - volumeFeedback, - quickSoundPresets, - selectedPresetId, - onClosePopover, - onOpenInbox, - onOpenControlCenter, - onToggleNotes, - onToggleSound, - onNoteDraftChange, - onNoteSubmit, - onToggleMute, - onVolumeChange, - onVolumeKeyDown, - onSelectPreset, -}: FocusModeAnchorsProps) => { - if (!isFocusMode) { - return null; - } - - return ( - <> - {openPopover ? ( - - - {/* Tooltip */} -
- - Brain Dump - -
- - {openPopover === 'notes' ? ( -
-
- -
-
- ) : null} - - - {/* Standard Tools */} -
- - {/* Sound Toggle */} -
- - - {/* Tooltip */} -
- - μ‚¬μš΄λ“œ - -
- - {openPopover === 'sound' ? ( -
- -
- ) : null} -
- -
- - {/* Inbox Button */} -
- -
- - {copy.space.inbox.openInboxTitle} - -
-
- - {/* Control Center Button */} -
- -
- - {copy.space.rightRail.openQuickControlsTitle} - -
-
- -
- -
- - ); -}; diff --git a/src/widgets/space-tools-dock/ui/SpaceToolsDockWidget.tsx b/src/widgets/space-tools-dock/ui/SpaceToolsDockWidget.tsx deleted file mode 100644 index 91e0ba7..0000000 --- a/src/widgets/space-tools-dock/ui/SpaceToolsDockWidget.tsx +++ /dev/null @@ -1,278 +0,0 @@ -'use client'; -import { useEffect, useMemo } from 'react'; -import type { SceneAssetMap } from '@/entities/media'; -import type { SceneTheme } from '@/entities/scene'; -import { SOUND_PRESETS, type RecentThought, type TimerPreset } from '@/entities/session'; -import { copy } from '@/shared/i18n'; -import { ExitHoldButton } from '@/features/exit-hold'; -import { ManagePlanSheetContent, PaywallSheetContent } from '@/features/paywall-sheet'; -import { PlanPill } from '@/features/plan-pill'; -import type { HudStatusLinePayload } from '@/shared/lib/useHudStatusLine'; -import { cn } from '@/shared/lib/cn'; -import { ControlCenterSheetWidget } from '@/widgets/control-center-sheet'; -import { SpaceSideSheet } from '@/widgets/space-sheet-shell'; -import { getQuickSoundPresets } from '../model/getQuickSoundPresets'; -import { UTILITY_PANEL_TITLE } from './constants'; -import { FocusModeAnchors } from './FocusModeAnchors'; -import { InboxToolPanel } from './panels/InboxToolPanel'; -import { useSpaceToolsDockState } from '../model/useSpaceToolsDockState'; -import { useSpaceToolsDockHandlers } from '../model/useSpaceToolsDockHandlers'; - -interface SpaceToolsDockWidgetProps { - isFocusMode: boolean; - scenes: SceneTheme[]; - sceneAssetMap?: SceneAssetMap; - selectedSceneId: string; - selectedTimerLabel: string; - timerPresets: TimerPreset[]; - selectedPresetId: string; - soundVolume: number; - onSetSoundVolume: (volume: number) => void; - isSoundMuted: boolean; - onSetSoundMuted: (nextMuted: boolean) => void; - thoughts: RecentThought[]; - thoughtCount: number; - sceneRecommendedSoundLabel: string; - sceneRecommendedTimerLabel: string; - onSceneSelect: (sceneId: string) => void; - onTimerSelect: (timerLabel: string) => void; - onQuickSoundSelect: (presetId: string) => void; - onCaptureThought: (note: string) => RecentThought | null; - onDeleteThought: (thoughtId: string) => RecentThought | null; - onSetThoughtCompleted: (thoughtId: string, isCompleted: boolean) => RecentThought | null; - onRestoreThought: (thought: RecentThought) => void; - onRestoreThoughts: (thoughts: RecentThought[]) => void; - onClearInbox: () => RecentThought[]; - onStatusMessage: (payload: HudStatusLinePayload) => void; - onExitRequested: () => void; -} - -export const SpaceToolsDockWidget = ({ - isFocusMode, - scenes, - sceneAssetMap, - selectedSceneId, - selectedTimerLabel, - timerPresets, - selectedPresetId, - soundVolume, - onSetSoundVolume, - isSoundMuted, - onSetSoundMuted, - thoughts, - thoughtCount, - sceneRecommendedSoundLabel, - sceneRecommendedTimerLabel, - onSceneSelect, - onTimerSelect, - onQuickSoundSelect, - onCaptureThought, - onDeleteThought, - onSetThoughtCompleted, - onRestoreThought, - onRestoreThoughts, - onClearInbox, - onStatusMessage, - onExitRequested, -}: SpaceToolsDockWidgetProps) => { - const { controlCenter } = copy.space; - - const { - openPopover, - setOpenPopover, - utilityPanel, - setUtilityPanel, - autoHideControls, - setAutoHideControls, - isIdle, - setIdle, - volumeFeedback, - showVolumeFeedback, - } = useSpaceToolsDockState({ isFocusMode }); - - const { - noteDraft, - setNoteDraft, - plan, - openUtilityPanel, - handleNoteSubmit, - handleInboxComplete, - handleInboxDelete, - handleInboxClear, - handlePlanPillClick, - handleLockedClick, - handleSelectProFeature, - handleStartPro, - handleVolumeChange, - handleVolumeKeyDown, - } = useSpaceToolsDockHandlers({ - setIdle, - setOpenPopover, - setUtilityPanel, - onCaptureThought, - onDeleteThought, - onSetThoughtCompleted, - onRestoreThought, - onRestoreThoughts, - onClearInbox, - onStatusMessage, - onSetSoundVolume, - onSetSoundMuted, - soundVolume, - isSoundMuted, - showVolumeFeedback, - }); - - const selectedSoundLabel = useMemo(() => { - return ( - SOUND_PRESETS.find((preset) => preset.id === selectedPresetId)?.label ?? SOUND_PRESETS[0]?.label ?? copy.common.default - ); - }, [selectedPresetId]); - - const quickSoundPresets = useMemo(() => { - return getQuickSoundPresets(SOUND_PRESETS); - }, []); - - useEffect(() => { - if (!openPopover) { - return; - } - - const handleEscape = (event: globalThis.KeyboardEvent) => { - if (event.key === 'Escape') { - setOpenPopover(null); - } - }; - - document.addEventListener('keydown', handleEscape); - - return () => { - document.removeEventListener('keydown', handleEscape); - }; - }, [openPopover, setOpenPopover]); - - return ( - <> -
-
-
- βŽ‹ -
-
- -
-
-
- - setOpenPopover(null)} - onOpenInbox={() => openUtilityPanel('inbox')} - onOpenControlCenter={() => openUtilityPanel('control-center')} - onToggleNotes={() => { - setIdle(false); - setOpenPopover((current) => (current === 'notes' ? null : 'notes')); - }} - onToggleSound={() => { - setIdle(false); - setOpenPopover((current) => (current === 'sound' ? null : 'sound')); - }} - onNoteDraftChange={setNoteDraft} - onNoteSubmit={handleNoteSubmit} - onToggleMute={() => { - const nextMuted = !isSoundMuted; - onSetSoundMuted(nextMuted); - showVolumeFeedback(nextMuted ? 0 : soundVolume); - }} - onVolumeChange={handleVolumeChange} - onVolumeKeyDown={handleVolumeKeyDown} - onSelectPreset={(presetId) => { - onQuickSoundSelect(presetId); - setOpenPopover(null); - }} - /> - - - ) : undefined - } - widthClassName={utilityPanel === 'control-center' ? 'w-[min(408px,94vw)]' : undefined} - overlayClassName={utilityPanel === 'control-center' ? 'bg-slate-950/6 backdrop-blur-none' : undefined} - onClose={() => setUtilityPanel(null)} - > - {utilityPanel === 'control-center' ? ( - { - onSceneSelect(sceneId); - }} - onSelectTimer={(label) => { - onTimerSelect(label); - }} - onSelectSound={onQuickSoundSelect} - onSelectProFeature={handleSelectProFeature} - onLockedClick={handleLockedClick} - /> - ) : null} - - {utilityPanel === 'inbox' ? ( - - ) : null} - - {utilityPanel === 'paywall' ? ( - setUtilityPanel(null)} - /> - ) : null} - - {utilityPanel === 'manage-plan' ? ( - setUtilityPanel(null)} - onManage={() => onStatusMessage({ message: copy.space.toolsDock.manageSubscriptionMock })} - onRestore={() => onStatusMessage({ message: copy.space.toolsDock.restorePurchaseMock })} - /> - ) : null} - - - ); -}; diff --git a/src/widgets/space-tools-dock/ui/constants.tsx b/src/widgets/space-tools-dock/ui/constants.tsx deleted file mode 100644 index 8eecb9d..0000000 --- a/src/widgets/space-tools-dock/ui/constants.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import type { SpaceUtilityPanelId } from '../model/types'; -import { copy } from '@/shared/i18n'; - -export const ANCHOR_ICON = { - sound: ( - - - - - ), - notes: ( - - - - - - ), -}; - -export const RAIL_ICON = { - controlCenter: ( - - - - - - - ), - inbox: ( - - - - - ), -}; - -export const UTILITY_PANEL_TITLE: Record = { - 'control-center': copy.space.toolsDock.utilityPanelTitle['control-center'], - inbox: copy.space.toolsDock.utilityPanelTitle.inbox, - paywall: copy.space.toolsDock.utilityPanelTitle.paywall, - 'manage-plan': copy.space.toolsDock.utilityPanelTitle['manage-plan'], -}; - -export const formatThoughtCount = (count: number) => { - if (count < 1) { - return '0'; - } - - if (count > 9) { - return '9+'; - } - - return String(count); -}; diff --git a/src/widgets/space-tools-dock/ui/panels/InboxToolPanel.tsx b/src/widgets/space-tools-dock/ui/panels/InboxToolPanel.tsx deleted file mode 100644 index e57d836..0000000 --- a/src/widgets/space-tools-dock/ui/panels/InboxToolPanel.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { useState } from 'react'; -import type { RecentThought } from '@/entities/session'; -import { copy } from '@/shared/i18n'; -import { InboxList } from '@/features/inbox'; - -interface InboxToolPanelProps { - thoughts: RecentThought[]; - onCompleteThought: (thought: RecentThought) => void; - onDeleteThought: (thought: RecentThought) => void; - onClear: () => void; -} - -export const InboxToolPanel = ({ - thoughts, - onCompleteThought, - onDeleteThought, - onClear, -}: InboxToolPanelProps) => { - const [confirmOpen, setConfirmOpen] = useState(false); - - return ( -
-
-

{copy.space.inbox.readOnly}

- -
- - - {confirmOpen ? ( -
-
-

{copy.space.inbox.clearConfirmTitle}

-

{copy.space.inbox.clearConfirmDescription}

-
- - -
-
-
- ) : null} -
- ); -}; diff --git a/src/widgets/space-tools-dock/ui/panels/SettingsToolPanel.tsx b/src/widgets/space-tools-dock/ui/panels/SettingsToolPanel.tsx deleted file mode 100644 index 93a6e3d..0000000 --- a/src/widgets/space-tools-dock/ui/panels/SettingsToolPanel.tsx +++ /dev/null @@ -1,137 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import type { SceneTheme } from '@/entities/scene'; -import type { TimerPreset } from '@/entities/session'; -import { copy } from '@/shared/i18n'; -import { DEFAULT_PRESET_OPTIONS } from '@/shared/config/settingsOptions'; -import { cn } from '@/shared/lib/cn'; - -interface SettingsToolPanelProps { - scenes: SceneTheme[]; - selectedSceneId: string; - selectedTimerLabel: string; - timerPresets: TimerPreset[]; - onSelectScene: (sceneId: string) => void; - onSelectTimer: (timerLabel: string) => void; -} - -export const SettingsToolPanel = ({ - scenes, - selectedSceneId, - selectedTimerLabel, - timerPresets, - onSelectScene, - onSelectTimer, -}: SettingsToolPanelProps) => { - const { settingsPanel } = copy.space; - const [reduceMotion, setReduceMotion] = useState(false); - const [defaultPresetId, setDefaultPresetId] = useState< - (typeof DEFAULT_PRESET_OPTIONS)[number]['id'] - >(DEFAULT_PRESET_OPTIONS[0].id); - - return ( -
-
-
-
-

{settingsPanel.reduceMotion}

-

{settingsPanel.reduceMotionDescription}

-
- -
-
- -
-

{settingsPanel.background}

-

{settingsPanel.backgroundDescription}

-
- {scenes.slice(0, 4).map((scene) => { - const selected = scene.id === selectedSceneId; - - return ( - - ); - })} -
-
- -
-

{settingsPanel.timerPreset}

-

{settingsPanel.timerPresetDescription}

-
- {timerPresets.slice(0, 3).map((preset) => { - const selected = preset.label === selectedTimerLabel; - - return ( - - ); - })} -
-
- -
-

{settingsPanel.defaultPreset}

-
- {DEFAULT_PRESET_OPTIONS.map((preset) => ( - - ))} -
-
-
- ); -}; diff --git a/src/widgets/space-tools-dock/ui/panels/StatsToolPanel.tsx b/src/widgets/space-tools-dock/ui/panels/StatsToolPanel.tsx deleted file mode 100644 index 45cce03..0000000 --- a/src/widgets/space-tools-dock/ui/panels/StatsToolPanel.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { TODAY_STATS, WEEKLY_STATS } from '@/entities/session'; -import { copy } from '@/shared/i18n'; - -export const StatsToolPanel = () => { - const previewStats = [TODAY_STATS[0], TODAY_STATS[1], WEEKLY_STATS[0], WEEKLY_STATS[2]]; - - return ( -
-

{copy.space.statsPanel.description}

- -
- {previewStats.map((stat) => ( -
-

{stat.label}

-

{stat.value}

-

{stat.delta}

-
- ))} -
- -
-
-

{copy.space.statsPanel.graphPlaceholder}

-
-
- ); -}; diff --git a/src/widgets/space-tools-dock/ui/popovers/QuickNotesPopover.tsx b/src/widgets/space-tools-dock/ui/popovers/QuickNotesPopover.tsx deleted file mode 100644 index 421c9eb..0000000 --- a/src/widgets/space-tools-dock/ui/popovers/QuickNotesPopover.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { copy } from '@/shared/i18n'; - -interface QuickNotesPopoverProps { - noteDraft: string; - onDraftChange: (value: string) => void; - onDraftEnter: () => void; - onSubmit: () => void; -} - -export const QuickNotesPopover = ({ - noteDraft, - onDraftChange, - onDraftEnter, - onSubmit, -}: QuickNotesPopoverProps) => { - return ( -
-

{copy.space.quickNotes.title}

-
- onDraftChange(event.target.value)} - onKeyDown={(event) => { - if (event.key !== 'Enter') { - return; - } - - event.preventDefault(); - onDraftEnter(); - }} - placeholder={copy.space.quickNotes.placeholder} - autoFocus - className="w-full border-b border-white/20 bg-transparent pb-2 text-sm text-white placeholder:text-white/30 transition-colors focus:border-white/60 focus:outline-none" - /> -
-

{copy.space.quickNotes.hint}

- -
-
-
- ); -}; diff --git a/src/widgets/space-tools-dock/ui/popovers/QuickSoundPopover.tsx b/src/widgets/space-tools-dock/ui/popovers/QuickSoundPopover.tsx deleted file mode 100644 index 5120271..0000000 --- a/src/widgets/space-tools-dock/ui/popovers/QuickSoundPopover.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { cn } from '@/shared/lib/cn'; -import type { SoundPreset } from '@/entities/session'; -import type { KeyboardEvent as ReactKeyboardEvent } from 'react'; -import { copy } from '@/shared/i18n'; - -interface QuickSoundPopoverProps { - selectedSoundLabel: string; - isSoundMuted: boolean; - soundVolume: number; - volumeFeedback: string | null; - quickSoundPresets: SoundPreset[]; - selectedPresetId: string; - onToggleMute: () => void; - onVolumeChange: (nextVolume: number) => void; - onVolumeKeyDown: (event: ReactKeyboardEvent) => void; - onSelectPreset: (presetId: string) => void; -} - -export const QuickSoundPopover = ({ - selectedSoundLabel, - isSoundMuted, - soundVolume, - volumeFeedback, - quickSoundPresets, - selectedPresetId, - onToggleMute, - onVolumeChange, - onVolumeKeyDown, - onSelectPreset, -}: QuickSoundPopoverProps) => { - return ( -
-
-

{copy.space.quickSound.currentSound}

- - {volumeFeedback ?? (isSoundMuted ? '0%' : `${soundVolume}%`)} - -
-

{selectedSoundLabel}

- -
-
- -
- onVolumeChange(Number(event.target.value))} - onKeyDown={onVolumeKeyDown} - aria-label={copy.space.quickSound.volumeAriaLabel} - className="absolute z-10 w-full cursor-pointer appearance-none bg-transparent accent-white outline-none [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:shadow-md" - /> -
-
-
-
-
-
- -
-

{copy.space.quickSound.quickSwitch}

-
- {quickSoundPresets.map((preset) => { - const selected = preset.id === selectedPresetId; - - return ( - - ); - })} -
-
-
- ); -}; diff --git a/src/widgets/space-workspace/model/useAwayReturnRecovery.ts b/src/widgets/space-workspace/model/useAwayReturnRecovery.ts deleted file mode 100644 index f5efe4e..0000000 --- a/src/widgets/space-workspace/model/useAwayReturnRecovery.ts +++ /dev/null @@ -1,184 +0,0 @@ -'use client'; - -import { useCallback, useEffect, useRef, useState } from 'react'; -import type { FocusSession } from '@/features/focus-session'; - -const AWAY_HIDDEN_THRESHOLD_MS = 20_000; -const AWAY_SLEEP_GAP_THRESHOLD_MS = 90_000; -const HEARTBEAT_INTERVAL_MS = 15_000; - -export type ReturnPromptMode = 'focus' | 'break'; - -interface UseAwayReturnRecoveryParams { - currentSession: FocusSession | null; - isBootstrapping: boolean; - syncCurrentSession: () => Promise; -} - -interface UseAwayReturnRecoveryResult { - returnPromptMode: ReturnPromptMode | null; - dismissReturnPrompt: () => void; -} - -export const useAwayReturnRecovery = ({ - currentSession, - isBootstrapping, - syncCurrentSession, -}: UseAwayReturnRecoveryParams): UseAwayReturnRecoveryResult => { - const [returnPromptMode, setReturnPromptMode] = useState(null); - const hiddenAtRef = useRef(null); - const awayCandidateRef = useRef(false); - const heartbeatAtRef = useRef(Date.now()); - const isHandlingReturnRef = useRef(false); - - const isRunningFocusSession = - currentSession?.state === 'running' && currentSession.phase === 'focus'; - - const clearAwayCandidate = useCallback(() => { - hiddenAtRef.current = null; - awayCandidateRef.current = false; - }, []); - - const dismissReturnPrompt = useCallback(() => { - setReturnPromptMode(null); - clearAwayCandidate(); - }, [clearAwayCandidate]); - - useEffect(() => { - heartbeatAtRef.current = Date.now(); - }, [currentSession?.id]); - - useEffect(() => { - if (!isRunningFocusSession) { - clearAwayCandidate(); - return; - } - - const intervalId = window.setInterval(() => { - if (document.visibilityState === 'visible') { - heartbeatAtRef.current = Date.now(); - } - }, HEARTBEAT_INTERVAL_MS); - - return () => { - window.clearInterval(intervalId); - }; - }, [clearAwayCandidate, isRunningFocusSession]); - - useEffect(() => { - if (currentSession?.state !== 'running') { - if (returnPromptMode === 'focus') { - setReturnPromptMode(null); - } - return; - } - - if (currentSession.phase !== 'break' && returnPromptMode === 'break') { - setReturnPromptMode(null); - } - }, [currentSession?.phase, currentSession?.state, returnPromptMode]); - - useEffect(() => { - if (isBootstrapping) { - return; - } - - const maybeHandleReturn = async () => { - if (isHandlingReturnRef.current) { - return; - } - - const hiddenDuration = - hiddenAtRef.current == null ? 0 : Date.now() - hiddenAtRef.current; - const sleepGap = Date.now() - heartbeatAtRef.current; - - if (!awayCandidateRef.current) { - if ( - isRunningFocusSession && - document.visibilityState === 'visible' && - sleepGap >= AWAY_SLEEP_GAP_THRESHOLD_MS - ) { - awayCandidateRef.current = true; - } else { - return; - } - } - - if (hiddenAtRef.current != null && hiddenDuration < AWAY_HIDDEN_THRESHOLD_MS) { - clearAwayCandidate(); - heartbeatAtRef.current = Date.now(); - return; - } - - isHandlingReturnRef.current = true; - - try { - const syncedSession = await syncCurrentSession(); - const resolvedSession = syncedSession ?? currentSession; - - clearAwayCandidate(); - heartbeatAtRef.current = Date.now(); - - if (!resolvedSession || resolvedSession.state !== 'running') { - setReturnPromptMode(null); - return; - } - - if (resolvedSession.phase === 'focus') { - setReturnPromptMode('focus'); - return; - } - - if (resolvedSession.phase === 'break') { - setReturnPromptMode('break'); - return; - } - - setReturnPromptMode(null); - } finally { - isHandlingReturnRef.current = false; - } - }; - - const handleVisibilityChange = () => { - if (document.visibilityState === 'hidden') { - if (isRunningFocusSession) { - hiddenAtRef.current = Date.now(); - awayCandidateRef.current = true; - } - - return; - } - - void maybeHandleReturn(); - }; - - const handlePageHide = () => { - if (isRunningFocusSession) { - hiddenAtRef.current = Date.now(); - awayCandidateRef.current = true; - } - }; - - const handleWindowFocus = () => { - void maybeHandleReturn(); - }; - - document.addEventListener('visibilitychange', handleVisibilityChange); - window.addEventListener('pagehide', handlePageHide); - window.addEventListener('focus', handleWindowFocus); - window.addEventListener('pageshow', handleWindowFocus); - - return () => { - document.removeEventListener('visibilitychange', handleVisibilityChange); - window.removeEventListener('pagehide', handlePageHide); - window.removeEventListener('focus', handleWindowFocus); - window.removeEventListener('pageshow', handleWindowFocus); - }; - }, [clearAwayCandidate, currentSession, isBootstrapping, isRunningFocusSession, syncCurrentSession]); - - return { - returnPromptMode, - dismissReturnPrompt, - }; -};