fix(space): remove stale setup drawer flow

This commit is contained in:
2026-03-17 14:42:56 +09:00
parent 13a94ef42d
commit f21129fc5d
11 changed files with 6 additions and 536 deletions

View File

@@ -92,7 +92,7 @@ export interface StartFocusSessionRequest {
soundPresetId?: string | null;
focusPlanItemId?: string;
microStep?: string | null;
entryPoint?: 'space-setup' | 'goal-complete' | 'resume-restore';
entryPoint?: 'space-setup' | 'goal-complete';
}
export interface CompleteFocusSessionRequest {

View File

@@ -9,28 +9,6 @@ export const spaceEn = {
placeholder: 'e.g. Draft the first page of the contract',
hint: 'Keep it small. Just the next concrete piece.',
},
setup: {
...koSpace.setup,
panelAriaLabel: 'Focus entry panel',
eyebrow: 'Execution Setup',
title: 'Set the goal, time, and atmosphere. Then step in.',
description: 'Pick a goal, background, timer, and sound. Then move straight into the focus stage.',
resumeTitle: 'Pick up the last block',
startFresh: 'Start fresh',
resumePrepare: 'Prepare to resume',
sceneLabel: 'Background',
timerLabel: 'Time',
soundLabel: 'Sound',
reviewTeaserEyebrow: 'Weekly Review',
reviewTeaserTitle: 'Take another look at this week?',
reviewTeaserTitlePro: 'Review this week and reopen the rhythm that worked best?',
reviewTeaserHelper: 'You can jump right back in, or pause for a quick weekly review first.',
reviewTeaserHelperPro: 'You can jump right back in, or check this weeks flow and recommended atmosphere first.',
reviewTeaserCta: 'Open weekly review',
reviewTeaserDismiss: 'Later',
readyHint: 'Add a goal to unlock the start flow.',
openFocusScreen: 'Open focus stage',
},
timerHud: {
...koSpace.timerHud,
actions: [

View File

@@ -5,27 +5,6 @@ export const space = {
placeholder: '예: 계약서 1페이지 정리',
hint: '크게 말고, 바로 다음 한 조각.',
},
setup: {
panelAriaLabel: '집중 시작 패널',
eyebrow: 'Execution Setup',
title: '이번 세션만 가볍게 맞추고 들어가요.',
description: '목표, 배경, 타이머, 사운드만 정하고 바로 실행 화면으로 들어갑니다.',
resumeTitle: '지난 한 조각 이어서',
startFresh: '새로 시작',
resumePrepare: '이어서 준비',
sceneLabel: '배경',
timerLabel: '타이머',
soundLabel: '사운드',
reviewTeaserEyebrow: 'Weekly Review',
reviewTeaserTitle: '이번 주 review를 다시 볼까요?',
reviewTeaserTitlePro: '이번 주 흐름과 잘 맞았던 ritual을 다시 볼까요?',
reviewTeaserHelper: '지금은 바로 다시 시작해도 괜찮고, 원하면 주간 review를 잠깐 보고 갈 수 있어요.',
reviewTeaserHelperPro: '원하면 이번 주 흐름과 추천 ritual을 다시 보고 다음 세션으로 이어갈 수 있어요.',
reviewTeaserCta: '주간 review 보기',
reviewTeaserDismiss: '나중에',
readyHint: '목표를 적으면 시작할 수 있어요.',
openFocusScreen: '실행 화면 열기',
},
timerHud: {
actions: [
{ id: 'start', label: '시작', icon: '▶' },

View File

@@ -1 +0,0 @@
export * from './ui/SpaceSetupDrawerWidget';

View File

@@ -1,355 +0,0 @@
'use client';
import Link from 'next/link';
import { useEffect, useMemo, useRef, useState, type FormEvent } from 'react';
import type { SceneAssetMap } from '@/entities/media';
import type { SceneTheme } from '@/entities/scene';
import type {
GoalChip,
SoundPreset,
} from '@/entities/session';
import { copy } from '@/shared/i18n';
import { SceneSelectCarousel } from '@/features/scene-select';
import { SessionGoalField } from '@/features/session-goal';
import { Button } from '@/shared/ui';
import { cn } from '@/shared/lib/cn';
type SelectionPopover = 'space' | 'timer' | 'sound';
interface SpaceSetupDrawerWidgetProps {
open: boolean;
scenes: SceneTheme[];
sceneAssetMap?: SceneAssetMap;
selectedSceneId: string;
selectedDurationLabel: string;
selectedSoundPresetId: string;
goalInput: string;
selectedGoalId: string | null;
goalChips: GoalChip[];
soundPresets: SoundPreset[];
durationOptions: readonly number[];
canStart: boolean;
onSceneSelect: (sceneId: string) => void;
onDurationSelect: (durationMinutes: number) => void;
onSoundSelect: (soundPresetId: string) => void;
onGoalChange: (value: string) => void;
onGoalChipSelect: (chip: GoalChip) => void;
onStart: () => void;
resumeHint?: {
goal: string;
onResume: () => void;
onStartFresh: () => void;
};
reviewTeaser?: {
title: string;
summary: string;
ctaHref: string;
ctaLabel: string;
onDismiss: () => void;
};
}
interface SummaryChipProps {
label: string;
value: string;
open: boolean;
onClick: () => void;
}
const SummaryChip = ({ label, value, open, onClick }: SummaryChipProps) => {
return (
<button
type="button"
onClick={onClick}
className={cn(
'inline-flex items-center gap-1 rounded-full border px-2.5 py-1 text-[11px] transition-colors',
open
? 'border-sky-200/42 bg-sky-200/14 text-white/92'
: 'border-white/14 bg-white/[0.04] text-white/80 hover:bg-white/[0.09]',
)}
>
<span className="text-white/62">{label}</span>
<span className="max-w-[124px] truncate text-white/92">{value}</span>
<span aria-hidden className="text-white/56"></span>
</button>
);
};
export const SpaceSetupDrawerWidget = ({
open,
scenes,
sceneAssetMap,
selectedSceneId,
selectedDurationLabel,
selectedSoundPresetId,
goalInput,
selectedGoalId,
goalChips,
soundPresets,
durationOptions,
canStart,
onSceneSelect,
onDurationSelect,
onSoundSelect,
onGoalChange,
onGoalChipSelect,
onStart,
resumeHint,
reviewTeaser,
}: SpaceSetupDrawerWidgetProps) => {
const { setup } = copy.space;
const [openPopover, setOpenPopover] = useState<SelectionPopover | null>(null);
const panelRef = useRef<HTMLDivElement | null>(null);
const selectedScene = useMemo(() => {
return scenes.find((scene) => scene.id === selectedSceneId) ?? scenes[0];
}, [scenes, selectedSceneId]);
const selectedSoundLabel = useMemo(() => {
return (
soundPresets.find((preset) => preset.id === selectedSoundPresetId)?.label ??
soundPresets[0]?.label ??
copy.common.default
);
}, [selectedSoundPresetId, soundPresets]);
useEffect(() => {
if (!openPopover) {
return;
}
const handleEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
setOpenPopover(null);
}
};
const handlePointerDown = (event: MouseEvent) => {
const target = event.target as Node;
if (!panelRef.current?.contains(target)) {
setOpenPopover(null);
}
};
document.addEventListener('keydown', handleEscape);
document.addEventListener('mousedown', handlePointerDown);
return () => {
document.removeEventListener('keydown', handleEscape);
document.removeEventListener('mousedown', handlePointerDown);
};
}, [openPopover]);
if (!open) {
return null;
}
const togglePopover = (popover: SelectionPopover) => {
setOpenPopover((current) => (current === popover ? null : popover));
};
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (!canStart) {
return;
}
onStart();
};
return (
<section
className="fixed left-1/2 top-1/2 z-40 w-[min(428px,92vw)] -translate-x-1/2 -translate-y-1/2"
aria-label={setup.panelAriaLabel}
>
<div
ref={panelRef}
className="rounded-3xl border border-white/14 bg-[linear-gradient(160deg,rgba(15,23,42,0.68)_0%,rgba(8,13,27,0.56)_100%)] p-4 text-white shadow-[0_22px_52px_rgba(2,6,23,0.38)] backdrop-blur-2xl sm:p-5"
>
<header className="mb-3 space-y-1">
<p className="text-[10px] uppercase tracking-[0.18em] text-white/48">{setup.eyebrow}</p>
<h1 className="text-[1.45rem] font-semibold leading-tight text-white">{setup.title}</h1>
<p className="text-xs text-white/60">{setup.description}</p>
</header>
{resumeHint ? (
<div className="mb-3 rounded-2xl border border-white/14 bg-black/22 px-3 py-2.5">
<p className="text-[11px] text-white/62">{setup.resumeTitle}</p>
<p className="mt-1 truncate text-sm text-white/88">{resumeHint.goal}</p>
<div className="mt-2 flex items-center justify-end gap-1.5">
<button
type="button"
onClick={resumeHint.onStartFresh}
className="rounded-full border border-white/16 bg-white/[0.04] px-2.5 py-1 text-[11px] text-white/72 transition-colors hover:bg-white/[0.1]"
>
{setup.startFresh}
</button>
<button
type="button"
onClick={resumeHint.onResume}
className="rounded-full border border-sky-200/34 bg-sky-200/14 px-2.5 py-1 text-[11px] text-white/90 transition-colors hover:bg-sky-200/22"
>
{setup.resumePrepare}
</button>
</div>
</div>
) : null}
<div className="relative mb-3">
<div className="flex flex-wrap gap-1.5">
<SummaryChip
label={setup.sceneLabel}
value={selectedScene?.name ?? copy.common.defaultBackground}
open={openPopover === 'space'}
onClick={() => togglePopover('space')}
/>
<SummaryChip
label={setup.timerLabel}
value={selectedDurationLabel}
open={openPopover === 'timer'}
onClick={() => togglePopover('timer')}
/>
<SummaryChip
label={setup.soundLabel}
value={selectedSoundLabel}
open={openPopover === 'sound'}
onClick={() => togglePopover('sound')}
/>
</div>
{openPopover === 'space' ? (
<div className="absolute left-0 right-0 top-[calc(100%+0.5rem)] z-20 rounded-2xl border border-white/14 bg-slate-950/80 p-2.5 shadow-[0_18px_44px_rgba(2,6,23,0.4)] backdrop-blur-xl animate-[popover-rise_220ms_ease-out] motion-reduce:animate-none">
<SceneSelectCarousel
scenes={scenes.slice(0, 4)}
selectedSceneId={selectedSceneId}
sceneAssetMap={sceneAssetMap}
onSelect={(sceneId) => {
onSceneSelect(sceneId);
setOpenPopover(null);
}}
/>
</div>
) : null}
{openPopover === 'timer' ? (
<div className="absolute left-0 top-[calc(100%+0.5rem)] z-20 rounded-2xl border border-white/14 bg-slate-950/80 p-3 shadow-[0_18px_44px_rgba(2,6,23,0.4)] backdrop-blur-xl animate-[popover-rise_220ms_ease-out] motion-reduce:animate-none">
<div className="flex flex-wrap gap-1.5">
{durationOptions.map((minutes) => {
const selected = `${minutes}m` === selectedDurationLabel;
return (
<button
key={minutes}
type="button"
onClick={() => {
onDurationSelect(minutes);
setOpenPopover(null);
}}
className={cn(
'rounded-full border px-2.5 py-0.5 text-[10px] transition-colors',
selected
? 'border-sky-200/34 bg-sky-200/14 text-white/90'
: 'border-white/12 bg-white/[0.03] text-white/66 hover:bg-white/8',
)}
>
{minutes}m
</button>
);
})}
</div>
</div>
) : null}
{openPopover === 'sound' ? (
<div className="absolute right-0 top-[calc(100%+0.5rem)] z-20 w-[min(300px,88vw)] rounded-2xl border border-white/14 bg-slate-950/80 p-3 shadow-[0_18px_44px_rgba(2,6,23,0.4)] backdrop-blur-xl animate-[popover-rise_220ms_ease-out] motion-reduce:animate-none">
<div className="flex flex-wrap gap-1.5">
{soundPresets.slice(0, 6).map((preset) => {
const selected = preset.id === selectedSoundPresetId;
return (
<button
key={preset.id}
type="button"
onClick={() => {
onSoundSelect(preset.id);
setOpenPopover(null);
}}
className={cn(
'rounded-full border px-2.5 py-0.5 text-[10px] transition-colors',
selected
? 'border-sky-200/34 bg-sky-200/14 text-white/90'
: 'border-white/12 bg-white/[0.03] text-white/66 hover:bg-white/8',
)}
>
{preset.label}
</button>
);
})}
</div>
</div>
) : null}
</div>
<form id="space-setup-form" className="space-y-3" onSubmit={handleSubmit}>
<SessionGoalField
autoFocus={open}
goalInput={goalInput}
selectedGoalId={selectedGoalId}
goalChips={goalChips}
onGoalChange={onGoalChange}
onGoalChipSelect={onGoalChipSelect}
/>
<div className="space-y-1.5 pt-1">
{!canStart ? <p className="text-[10px] text-white/56">{setup.readyHint}</p> : null}
<Button
type="submit"
form="space-setup-form"
size="full"
disabled={!canStart}
className={cn(
'h-10 rounded-xl !bg-sky-300/84 !text-slate-900 shadow-[0_8px_16px_rgba(125,211,252,0.24)] hover:!bg-sky-300 disabled:!bg-white/10 disabled:!text-white/42',
)}
>
{setup.openFocusScreen}
</Button>
</div>
</form>
{reviewTeaser ? (
<div className="mt-3 rounded-[1.25rem] border border-white/10 bg-black/16 px-3.5 py-3 backdrop-blur-md">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<p className="text-[10px] uppercase tracking-[0.16em] text-white/42">
{setup.reviewTeaserEyebrow}
</p>
<p className="mt-1 text-[13px] font-medium leading-[1.5] text-white/88">
{reviewTeaser.title}
</p>
<p className="mt-1 text-[11px] leading-[1.55] text-white/56">
{reviewTeaser.summary}
</p>
</div>
<button
type="button"
onClick={reviewTeaser.onDismiss}
className="shrink-0 rounded-full border border-white/12 bg-white/[0.04] px-2 py-1 text-[10px] text-white/62 transition hover:bg-white/[0.09] hover:text-white/84"
>
{setup.reviewTeaserDismiss}
</button>
</div>
<Link
href={reviewTeaser.ctaHref}
className="mt-3 inline-flex items-center rounded-full border border-white/12 bg-white/[0.06] px-3 py-1.5 text-[11px] font-medium text-white/82 transition hover:bg-white/[0.1] hover:text-white"
>
{reviewTeaser.ctaLabel}
</Link>
</div>
) : null}
</div>
</section>
);
};

View File

@@ -1,5 +1,5 @@
export type WorkspaceMode = 'setup' | 'focus';
export type SessionEntryPoint = 'space-setup' | 'goal-complete' | 'resume-restore';
export type SessionEntryPoint = 'space-setup' | 'goal-complete';
export type SelectionOverride = {
sound: boolean;
@@ -10,6 +10,5 @@ export interface StoredWorkspaceSelection {
sceneId?: string;
durationMinutes?: number;
soundPresetId?: string;
goal?: string;
override?: Partial<SelectionOverride>;
}

View File

@@ -86,8 +86,6 @@ export const useSpaceWorkspaceSelection = ({
const [goalInput, setGoalInput] = useState(initialGoal);
const [linkedFocusPlanItemId, setLinkedFocusPlanItemId] = useState<string | null>(initialFocusPlanItemId);
const [selectedGoalId, setSelectedGoalId] = useState<string | null>(null);
const [resumeGoal, setResumeGoal] = useState('');
const [showResumePrompt, setShowResumePrompt] = useState(false);
const [hasHydratedSelection, setHasHydratedSelection] = useState(false);
const [selectionOverride, setSelectionOverride] = useState<SelectionOverride>({
sound: false,
@@ -290,24 +288,19 @@ export const useSpaceWorkspaceSelection = ({
]);
const handleGoalChipSelect = useCallback((chip: GoalChip) => {
setShowResumePrompt(false);
setLinkedFocusPlanItemId(null);
setSelectedGoalId(chip.id);
setGoalInput(chip.label);
}, []);
const handleGoalChange = useCallback((value: string) => {
if (showResumePrompt) {
setShowResumePrompt(false);
}
setLinkedFocusPlanItemId(null);
setGoalInput(value);
if (value.trim().length === 0) {
setSelectedGoalId(null);
}
}, [showResumePrompt]);
}, []);
useEffect(() => {
const storedSelection = readStoredWorkspaceSelection();
@@ -325,7 +318,6 @@ export const useSpaceWorkspaceSelection = ({
storedSelection.soundPresetId && SOUND_PRESETS.some((preset) => preset.id === storedSelection.soundPresetId)
? storedSelection.soundPresetId
: null;
const restoredGoal = storedSelection.goal?.trim() ?? '';
const rafId = window.requestAnimationFrame(() => {
setSelectionOverride(restoredSelectionOverride);
@@ -341,11 +333,6 @@ export const useSpaceWorkspaceSelection = ({
setSelectedPresetId(restoredSoundPresetId);
}
if (restoredGoal.length > 0) {
setResumeGoal(restoredGoal);
setShowResumePrompt(true);
}
setHasHydratedSelection(true);
});
@@ -428,7 +415,6 @@ export const useSpaceWorkspaceSelection = ({
setGoalInput(currentSession.goal);
setLinkedFocusPlanItemId(currentSession.focusPlanItemId ?? null);
setSelectedGoalId(null);
setShowResumePrompt(false);
});
return () => {
@@ -442,8 +428,6 @@ export const useSpaceWorkspaceSelection = ({
selectedDurationMinutes,
selectedPresetId,
goalInput,
showResumePrompt,
resumeGoal,
selectionOverride,
});
@@ -463,8 +447,6 @@ export const useSpaceWorkspaceSelection = ({
goalInput,
linkedFocusPlanItemId,
selectedGoalId,
resumeGoal,
showResumePrompt,
hasHydratedSelection,
selectionOverride,
selectedScene,
@@ -474,8 +456,6 @@ export const useSpaceWorkspaceSelection = ({
setGoalInput,
setLinkedFocusPlanItemId,
setSelectedGoalId,
setShowResumePrompt,
setResumeGoal,
handleSelectScene,
handleSelectDuration,
handleSelectSound,

View File

@@ -61,7 +61,6 @@ interface UseSpaceWorkspaceSessionControlsParams {
setGoalInput: (value: string) => void;
setLinkedFocusPlanItemId: (value: string | null) => void;
setSelectedGoalId: (value: string | null) => void;
setShowResumePrompt: (value: boolean) => void;
}
export const useSpaceWorkspaceSessionControls = ({
@@ -94,7 +93,6 @@ export const useSpaceWorkspaceSessionControls = ({
setGoalInput,
setLinkedFocusPlanItemId,
setSelectedGoalId,
setShowResumePrompt,
}: UseSpaceWorkspaceSessionControlsParams) => {
const queuedFocusStatusMessageRef = useRef<string | null>(null);
const lastSoundPlaybackErrorRef = useRef<string | null>(null);
@@ -115,12 +113,11 @@ export const useSpaceWorkspaceSessionControls = ({
return;
}
setShowResumePrompt(false);
setPendingSessionEntryPoint(entryPoint);
setPreviewPlaybackState('paused');
setWorkspaceMode('focus');
queuedFocusStatusMessageRef.current = copy.space.workspace.readyToStart;
}, [setPendingSessionEntryPoint, setPreviewPlaybackState, setShowResumePrompt, setWorkspaceMode]);
}, [setPendingSessionEntryPoint, setPreviewPlaybackState, setWorkspaceMode]);
const startFocusFlow = useCallback(async () => {
const trimmedGoal = goalInput.trim();
@@ -266,7 +263,6 @@ export const useSpaceWorkspaceSessionControls = ({
setGoalInput(trimmedNextGoal);
setLinkedFocusPlanItemId(nextState.nextSession.focusPlanItemId ?? null);
setSelectedGoalId(null);
setShowResumePrompt(false);
setPendingSessionEntryPoint('goal-complete');
setPreviewPlaybackState('running');
setWorkspaceMode('focus');
@@ -289,7 +285,6 @@ export const useSpaceWorkspaceSessionControls = ({
setPendingSessionEntryPoint,
setPreviewPlaybackState,
setSelectedGoalId,
setShowResumePrompt,
setWorkspaceMode,
unlockPlayback,
]);
@@ -313,7 +308,6 @@ export const useSpaceWorkspaceSessionControls = ({
return null;
}
setShowResumePrompt(false);
setPendingSessionEntryPoint('space-setup');
setPreviewPlaybackState('paused');
setWorkspaceMode('setup');
@@ -325,7 +319,6 @@ export const useSpaceWorkspaceSessionControls = ({
pushStatusLine,
setPendingSessionEntryPoint,
setPreviewPlaybackState,
setShowResumePrompt,
setWorkspaceMode,
]);
@@ -348,7 +341,6 @@ export const useSpaceWorkspaceSessionControls = ({
return null;
}
setShowResumePrompt(false);
setPendingSessionEntryPoint('space-setup');
setPreviewPlaybackState('paused');
setWorkspaceMode('setup');
@@ -360,7 +352,6 @@ export const useSpaceWorkspaceSessionControls = ({
pushStatusLine,
setPendingSessionEntryPoint,
setPreviewPlaybackState,
setShowResumePrompt,
setWorkspaceMode,
]);
@@ -383,7 +374,6 @@ export const useSpaceWorkspaceSessionControls = ({
return null;
}
setShowResumePrompt(false);
setPendingSessionEntryPoint('space-setup');
setPreviewPlaybackState('paused');
setWorkspaceMode('setup');
@@ -395,7 +385,6 @@ export const useSpaceWorkspaceSessionControls = ({
pushStatusLine,
setPendingSessionEntryPoint,
setPreviewPlaybackState,
setShowResumePrompt,
setWorkspaceMode,
]);
@@ -477,7 +466,6 @@ export const useSpaceWorkspaceSessionControls = ({
setGoalInput(updatedSession.goal);
setLinkedFocusPlanItemId(updatedSession.focusPlanItemId ?? null);
setSelectedGoalId(null);
setShowResumePrompt(false);
return true;
}, [
currentSession,
@@ -485,7 +473,6 @@ export const useSpaceWorkspaceSessionControls = ({
setGoalInput,
setLinkedFocusPlanItemId,
setSelectedGoalId,
setShowResumePrompt,
updateCurrentIntent,
]);

View File

@@ -11,8 +11,6 @@ interface UseWorkspacePersistenceParams {
selectedDurationMinutes: number;
selectedPresetId: string;
goalInput: string;
showResumePrompt: boolean;
resumeGoal: string;
selectionOverride: SelectionOverride;
}
@@ -22,8 +20,6 @@ export const useWorkspacePersistence = ({
selectedDurationMinutes,
selectedPresetId,
goalInput,
showResumePrompt,
resumeGoal,
selectionOverride,
}: UseWorkspacePersistenceParams) => {
useEffect(() => {
@@ -31,30 +27,21 @@ export const useWorkspacePersistence = ({
return;
}
const normalizedGoal = goalInput.trim().length > 0
? goalInput.trim()
: showResumePrompt
? resumeGoal
: '';
window.localStorage.setItem(
WORKSPACE_SELECTION_STORAGE_KEY,
JSON.stringify({
sceneId: selectedScene.id,
durationMinutes: selectedDurationMinutes,
soundPresetId: selectedPresetId,
goal: normalizedGoal,
override: selectionOverride,
}),
);
}, [
goalInput,
hasHydratedSelection,
resumeGoal,
selectedPresetId,
selectedScene.id,
selectedDurationMinutes,
selectionOverride,
showResumePrompt,
]);
};

View File

@@ -14,7 +14,6 @@ export interface StoredWorkspaceSelection {
sceneId?: string;
durationMinutes?: number;
soundPresetId?: string;
goal?: string;
override?: Partial<SelectionOverride>;
}

View File

@@ -6,24 +6,20 @@ import {
preloadAssetImage,
useMediaCatalog,
} from "@/entities/media";
import { usePlanTier } from "@/entities/plan";
import { getSceneById, SCENE_THEMES } from "@/entities/scene";
import { GOAL_CHIPS, SOUND_PRESETS, useThoughtInbox } from "@/entities/session";
import { useThoughtInbox } from "@/entities/session";
import {
focusSessionApi,
type CompletionResult,
type CurrentSessionThought,
useFocusSessionEngine,
} from "@/features/focus-session";
import { useFocusStats } from "@/features/stats";
import {
useSoundPlayback,
useSoundPresetSelection,
} from "@/features/sound-preset";
import { useHudStatusLine } from "@/shared/lib/useHudStatusLine";
import { copy } from "@/shared/i18n";
import { SpaceFocusHudWidget } from "@/widgets/space-focus-hud";
import { SpaceSetupDrawerWidget } from "@/widgets/space-setup-drawer";
import { CompletionResultModal } from "@/widgets/space-focus-hud/ui/CompletionResultModal";
import {
findAtmosphereOptionForSelection,
@@ -35,7 +31,6 @@ import type { SessionEntryPoint, WorkspaceMode } from "../model/types";
import { useSpaceWorkspaceSelection } from "../model/useSpaceWorkspaceSelection";
import { useSpaceWorkspaceSessionControls } from "../model/useSpaceWorkspaceSessionControls";
import {
DURATION_SELECTION_OPTIONS,
resolveFocusTimeDisplayFromDurationMinutes,
resolveInitialDurationMinutes,
} from "../model/workspaceSelection";
@@ -55,8 +50,6 @@ export const SpaceWorkspaceWidget = () => {
usedFallbackManifest,
hasResolvedManifest,
} = useMediaCatalog();
const { isPro } = usePlanTier();
const { review, summary: weeklySummary } = useFocusStats();
const initialSceneId = useMemo(() => SCENE_THEMES[0].id, []);
const initialScene = useMemo(
@@ -88,7 +81,6 @@ export const SpaceWorkspaceWidget = () => {
>("paused");
const [pendingSessionEntryPoint, setPendingSessionEntryPoint] =
useState<SessionEntryPoint>("space-setup");
const [showReviewTeaserAfterComplete, setShowReviewTeaserAfterComplete] = useState(false);
const [, setCurrentSessionThoughts] = useState<CurrentSessionThought[]>([]);
const [pendingCompletionResult, setPendingCompletionResult] = useState<CompletionResult | null>(null);
@@ -190,31 +182,8 @@ export const SpaceWorkspaceWidget = () => {
setGoalInput: selection.setGoalInput,
setLinkedFocusPlanItemId: selection.setLinkedFocusPlanItemId,
setSelectedGoalId: selection.setSelectedGoalId,
setShowResumePrompt: selection.setShowResumePrompt,
});
const hasEnoughWeeklyData =
weeklySummary.last7Days.startedSessions >= 3 &&
(weeklySummary.last7Days.completedSessions >= 2 ||
weeklySummary.recovery.pausedSessions > 0);
const shouldShowSecondaryReviewTeaser =
workspaceMode === "setup" &&
showReviewTeaserAfterComplete &&
hasEnoughWeeklyData;
const didResolveEntryRouteRef = useRef(false);
const secondaryReviewTeaser = shouldShowSecondaryReviewTeaser
? {
title: isPro
? copy.space.setup.reviewTeaserTitlePro
: copy.space.setup.reviewTeaserTitle,
summary: isPro
? review.carryForward.keepDoing
: copy.space.setup.reviewTeaserHelper,
ctaHref: "/stats",
ctaLabel: copy.space.setup.reviewTeaserCta,
onDismiss: () => setShowReviewTeaserAfterComplete(false),
}
: undefined;
useEffect(() => {
if (!isBootstrapping && !currentSession && !pendingCompletionResult) {
@@ -302,58 +271,6 @@ export const SpaceWorkspaceWidget = () => {
<main className="relative flex-1" />
</div>
<SpaceSetupDrawerWidget
open={!isFocusMode && !isCompletionResultOpen}
scenes={selection.setupScenes}
sceneAssetMap={sceneAssetMap}
selectedSceneId={selection.selectedScene.id}
selectedDurationLabel={selection.selectedDurationLabel}
selectedSoundPresetId={selectedPresetId}
goalInput={selection.goalInput}
selectedGoalId={selection.selectedGoalId}
goalChips={GOAL_CHIPS}
soundPresets={SOUND_PRESETS}
durationOptions={DURATION_SELECTION_OPTIONS}
canStart={selection.canStart}
onSceneSelect={selection.handleSelectScene}
onDurationSelect={(durationMinutes) =>
selection.handleSelectDuration(durationMinutes, true)
}
onSoundSelect={(presetId) =>
selection.handleSelectSound(presetId, true)
}
onGoalChange={selection.handleGoalChange}
onGoalChipSelect={selection.handleGoalChipSelect}
onStart={() => {
setShowReviewTeaserAfterComplete(false);
controls.handleSetupFocusOpen();
}}
reviewTeaser={secondaryReviewTeaser}
resumeHint={
selection.showResumePrompt && selection.resumeGoal
? {
goal: selection.resumeGoal,
onResume: () => {
setShowReviewTeaserAfterComplete(false);
selection.setGoalInput(selection.resumeGoal);
selection.setSelectedGoalId(null);
selection.setShowResumePrompt(false);
controls.openFocusMode(
selection.resumeGoal,
"resume-restore",
);
},
onStartFresh: () => {
setShowReviewTeaserAfterComplete(false);
selection.setGoalInput("");
selection.setSelectedGoalId(null);
selection.setShowResumePrompt(false);
},
}
: undefined
}
/>
{isFocusMode ? (
<SpaceFocusHudWidget
sessionId={currentSession?.id ?? null}
@@ -429,7 +346,7 @@ export const SpaceWorkspaceWidget = () => {
onClose={() => {
setPendingCompletionResult(null);
setCurrentSessionThoughts([]);
router.replace('/app');
void router.replace('/app');
}}
/>