fix(space): remove stale setup drawer flow
This commit is contained in:
@@ -92,7 +92,7 @@ export interface StartFocusSessionRequest {
|
|||||||
soundPresetId?: string | null;
|
soundPresetId?: string | null;
|
||||||
focusPlanItemId?: string;
|
focusPlanItemId?: string;
|
||||||
microStep?: string | null;
|
microStep?: string | null;
|
||||||
entryPoint?: 'space-setup' | 'goal-complete' | 'resume-restore';
|
entryPoint?: 'space-setup' | 'goal-complete';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompleteFocusSessionRequest {
|
export interface CompleteFocusSessionRequest {
|
||||||
|
|||||||
@@ -9,28 +9,6 @@ export const spaceEn = {
|
|||||||
placeholder: 'e.g. Draft the first page of the contract',
|
placeholder: 'e.g. Draft the first page of the contract',
|
||||||
hint: 'Keep it small. Just the next concrete piece.',
|
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 week’s 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: {
|
timerHud: {
|
||||||
...koSpace.timerHud,
|
...koSpace.timerHud,
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
@@ -5,27 +5,6 @@ export const space = {
|
|||||||
placeholder: '예: 계약서 1페이지 정리',
|
placeholder: '예: 계약서 1페이지 정리',
|
||||||
hint: '크게 말고, 바로 다음 한 조각.',
|
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: {
|
timerHud: {
|
||||||
actions: [
|
actions: [
|
||||||
{ id: 'start', label: '시작', icon: '▶' },
|
{ id: 'start', label: '시작', icon: '▶' },
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export * from './ui/SpaceSetupDrawerWidget';
|
|
||||||
@@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
export type WorkspaceMode = 'setup' | 'focus';
|
export type WorkspaceMode = 'setup' | 'focus';
|
||||||
export type SessionEntryPoint = 'space-setup' | 'goal-complete' | 'resume-restore';
|
export type SessionEntryPoint = 'space-setup' | 'goal-complete';
|
||||||
|
|
||||||
export type SelectionOverride = {
|
export type SelectionOverride = {
|
||||||
sound: boolean;
|
sound: boolean;
|
||||||
@@ -10,6 +10,5 @@ export interface StoredWorkspaceSelection {
|
|||||||
sceneId?: string;
|
sceneId?: string;
|
||||||
durationMinutes?: number;
|
durationMinutes?: number;
|
||||||
soundPresetId?: string;
|
soundPresetId?: string;
|
||||||
goal?: string;
|
|
||||||
override?: Partial<SelectionOverride>;
|
override?: Partial<SelectionOverride>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,8 +86,6 @@ export const useSpaceWorkspaceSelection = ({
|
|||||||
const [goalInput, setGoalInput] = useState(initialGoal);
|
const [goalInput, setGoalInput] = useState(initialGoal);
|
||||||
const [linkedFocusPlanItemId, setLinkedFocusPlanItemId] = useState<string | null>(initialFocusPlanItemId);
|
const [linkedFocusPlanItemId, setLinkedFocusPlanItemId] = useState<string | null>(initialFocusPlanItemId);
|
||||||
const [selectedGoalId, setSelectedGoalId] = useState<string | null>(null);
|
const [selectedGoalId, setSelectedGoalId] = useState<string | null>(null);
|
||||||
const [resumeGoal, setResumeGoal] = useState('');
|
|
||||||
const [showResumePrompt, setShowResumePrompt] = useState(false);
|
|
||||||
const [hasHydratedSelection, setHasHydratedSelection] = useState(false);
|
const [hasHydratedSelection, setHasHydratedSelection] = useState(false);
|
||||||
const [selectionOverride, setSelectionOverride] = useState<SelectionOverride>({
|
const [selectionOverride, setSelectionOverride] = useState<SelectionOverride>({
|
||||||
sound: false,
|
sound: false,
|
||||||
@@ -290,24 +288,19 @@ export const useSpaceWorkspaceSelection = ({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const handleGoalChipSelect = useCallback((chip: GoalChip) => {
|
const handleGoalChipSelect = useCallback((chip: GoalChip) => {
|
||||||
setShowResumePrompt(false);
|
|
||||||
setLinkedFocusPlanItemId(null);
|
setLinkedFocusPlanItemId(null);
|
||||||
setSelectedGoalId(chip.id);
|
setSelectedGoalId(chip.id);
|
||||||
setGoalInput(chip.label);
|
setGoalInput(chip.label);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleGoalChange = useCallback((value: string) => {
|
const handleGoalChange = useCallback((value: string) => {
|
||||||
if (showResumePrompt) {
|
|
||||||
setShowResumePrompt(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
setLinkedFocusPlanItemId(null);
|
setLinkedFocusPlanItemId(null);
|
||||||
setGoalInput(value);
|
setGoalInput(value);
|
||||||
|
|
||||||
if (value.trim().length === 0) {
|
if (value.trim().length === 0) {
|
||||||
setSelectedGoalId(null);
|
setSelectedGoalId(null);
|
||||||
}
|
}
|
||||||
}, [showResumePrompt]);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const storedSelection = readStoredWorkspaceSelection();
|
const storedSelection = readStoredWorkspaceSelection();
|
||||||
@@ -325,7 +318,6 @@ export const useSpaceWorkspaceSelection = ({
|
|||||||
storedSelection.soundPresetId && SOUND_PRESETS.some((preset) => preset.id === storedSelection.soundPresetId)
|
storedSelection.soundPresetId && SOUND_PRESETS.some((preset) => preset.id === storedSelection.soundPresetId)
|
||||||
? storedSelection.soundPresetId
|
? storedSelection.soundPresetId
|
||||||
: null;
|
: null;
|
||||||
const restoredGoal = storedSelection.goal?.trim() ?? '';
|
|
||||||
const rafId = window.requestAnimationFrame(() => {
|
const rafId = window.requestAnimationFrame(() => {
|
||||||
setSelectionOverride(restoredSelectionOverride);
|
setSelectionOverride(restoredSelectionOverride);
|
||||||
|
|
||||||
@@ -341,11 +333,6 @@ export const useSpaceWorkspaceSelection = ({
|
|||||||
setSelectedPresetId(restoredSoundPresetId);
|
setSelectedPresetId(restoredSoundPresetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (restoredGoal.length > 0) {
|
|
||||||
setResumeGoal(restoredGoal);
|
|
||||||
setShowResumePrompt(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
setHasHydratedSelection(true);
|
setHasHydratedSelection(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -428,7 +415,6 @@ export const useSpaceWorkspaceSelection = ({
|
|||||||
setGoalInput(currentSession.goal);
|
setGoalInput(currentSession.goal);
|
||||||
setLinkedFocusPlanItemId(currentSession.focusPlanItemId ?? null);
|
setLinkedFocusPlanItemId(currentSession.focusPlanItemId ?? null);
|
||||||
setSelectedGoalId(null);
|
setSelectedGoalId(null);
|
||||||
setShowResumePrompt(false);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@@ -442,8 +428,6 @@ export const useSpaceWorkspaceSelection = ({
|
|||||||
selectedDurationMinutes,
|
selectedDurationMinutes,
|
||||||
selectedPresetId,
|
selectedPresetId,
|
||||||
goalInput,
|
goalInput,
|
||||||
showResumePrompt,
|
|
||||||
resumeGoal,
|
|
||||||
selectionOverride,
|
selectionOverride,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -463,8 +447,6 @@ export const useSpaceWorkspaceSelection = ({
|
|||||||
goalInput,
|
goalInput,
|
||||||
linkedFocusPlanItemId,
|
linkedFocusPlanItemId,
|
||||||
selectedGoalId,
|
selectedGoalId,
|
||||||
resumeGoal,
|
|
||||||
showResumePrompt,
|
|
||||||
hasHydratedSelection,
|
hasHydratedSelection,
|
||||||
selectionOverride,
|
selectionOverride,
|
||||||
selectedScene,
|
selectedScene,
|
||||||
@@ -474,8 +456,6 @@ export const useSpaceWorkspaceSelection = ({
|
|||||||
setGoalInput,
|
setGoalInput,
|
||||||
setLinkedFocusPlanItemId,
|
setLinkedFocusPlanItemId,
|
||||||
setSelectedGoalId,
|
setSelectedGoalId,
|
||||||
setShowResumePrompt,
|
|
||||||
setResumeGoal,
|
|
||||||
handleSelectScene,
|
handleSelectScene,
|
||||||
handleSelectDuration,
|
handleSelectDuration,
|
||||||
handleSelectSound,
|
handleSelectSound,
|
||||||
|
|||||||
@@ -61,7 +61,6 @@ interface UseSpaceWorkspaceSessionControlsParams {
|
|||||||
setGoalInput: (value: string) => void;
|
setGoalInput: (value: string) => void;
|
||||||
setLinkedFocusPlanItemId: (value: string | null) => void;
|
setLinkedFocusPlanItemId: (value: string | null) => void;
|
||||||
setSelectedGoalId: (value: string | null) => void;
|
setSelectedGoalId: (value: string | null) => void;
|
||||||
setShowResumePrompt: (value: boolean) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSpaceWorkspaceSessionControls = ({
|
export const useSpaceWorkspaceSessionControls = ({
|
||||||
@@ -94,7 +93,6 @@ export const useSpaceWorkspaceSessionControls = ({
|
|||||||
setGoalInput,
|
setGoalInput,
|
||||||
setLinkedFocusPlanItemId,
|
setLinkedFocusPlanItemId,
|
||||||
setSelectedGoalId,
|
setSelectedGoalId,
|
||||||
setShowResumePrompt,
|
|
||||||
}: UseSpaceWorkspaceSessionControlsParams) => {
|
}: UseSpaceWorkspaceSessionControlsParams) => {
|
||||||
const queuedFocusStatusMessageRef = useRef<string | null>(null);
|
const queuedFocusStatusMessageRef = useRef<string | null>(null);
|
||||||
const lastSoundPlaybackErrorRef = useRef<string | null>(null);
|
const lastSoundPlaybackErrorRef = useRef<string | null>(null);
|
||||||
@@ -115,12 +113,11 @@ export const useSpaceWorkspaceSessionControls = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setShowResumePrompt(false);
|
|
||||||
setPendingSessionEntryPoint(entryPoint);
|
setPendingSessionEntryPoint(entryPoint);
|
||||||
setPreviewPlaybackState('paused');
|
setPreviewPlaybackState('paused');
|
||||||
setWorkspaceMode('focus');
|
setWorkspaceMode('focus');
|
||||||
queuedFocusStatusMessageRef.current = copy.space.workspace.readyToStart;
|
queuedFocusStatusMessageRef.current = copy.space.workspace.readyToStart;
|
||||||
}, [setPendingSessionEntryPoint, setPreviewPlaybackState, setShowResumePrompt, setWorkspaceMode]);
|
}, [setPendingSessionEntryPoint, setPreviewPlaybackState, setWorkspaceMode]);
|
||||||
|
|
||||||
const startFocusFlow = useCallback(async () => {
|
const startFocusFlow = useCallback(async () => {
|
||||||
const trimmedGoal = goalInput.trim();
|
const trimmedGoal = goalInput.trim();
|
||||||
@@ -266,7 +263,6 @@ export const useSpaceWorkspaceSessionControls = ({
|
|||||||
setGoalInput(trimmedNextGoal);
|
setGoalInput(trimmedNextGoal);
|
||||||
setLinkedFocusPlanItemId(nextState.nextSession.focusPlanItemId ?? null);
|
setLinkedFocusPlanItemId(nextState.nextSession.focusPlanItemId ?? null);
|
||||||
setSelectedGoalId(null);
|
setSelectedGoalId(null);
|
||||||
setShowResumePrompt(false);
|
|
||||||
setPendingSessionEntryPoint('goal-complete');
|
setPendingSessionEntryPoint('goal-complete');
|
||||||
setPreviewPlaybackState('running');
|
setPreviewPlaybackState('running');
|
||||||
setWorkspaceMode('focus');
|
setWorkspaceMode('focus');
|
||||||
@@ -289,7 +285,6 @@ export const useSpaceWorkspaceSessionControls = ({
|
|||||||
setPendingSessionEntryPoint,
|
setPendingSessionEntryPoint,
|
||||||
setPreviewPlaybackState,
|
setPreviewPlaybackState,
|
||||||
setSelectedGoalId,
|
setSelectedGoalId,
|
||||||
setShowResumePrompt,
|
|
||||||
setWorkspaceMode,
|
setWorkspaceMode,
|
||||||
unlockPlayback,
|
unlockPlayback,
|
||||||
]);
|
]);
|
||||||
@@ -313,7 +308,6 @@ export const useSpaceWorkspaceSessionControls = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setShowResumePrompt(false);
|
|
||||||
setPendingSessionEntryPoint('space-setup');
|
setPendingSessionEntryPoint('space-setup');
|
||||||
setPreviewPlaybackState('paused');
|
setPreviewPlaybackState('paused');
|
||||||
setWorkspaceMode('setup');
|
setWorkspaceMode('setup');
|
||||||
@@ -325,7 +319,6 @@ export const useSpaceWorkspaceSessionControls = ({
|
|||||||
pushStatusLine,
|
pushStatusLine,
|
||||||
setPendingSessionEntryPoint,
|
setPendingSessionEntryPoint,
|
||||||
setPreviewPlaybackState,
|
setPreviewPlaybackState,
|
||||||
setShowResumePrompt,
|
|
||||||
setWorkspaceMode,
|
setWorkspaceMode,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -348,7 +341,6 @@ export const useSpaceWorkspaceSessionControls = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setShowResumePrompt(false);
|
|
||||||
setPendingSessionEntryPoint('space-setup');
|
setPendingSessionEntryPoint('space-setup');
|
||||||
setPreviewPlaybackState('paused');
|
setPreviewPlaybackState('paused');
|
||||||
setWorkspaceMode('setup');
|
setWorkspaceMode('setup');
|
||||||
@@ -360,7 +352,6 @@ export const useSpaceWorkspaceSessionControls = ({
|
|||||||
pushStatusLine,
|
pushStatusLine,
|
||||||
setPendingSessionEntryPoint,
|
setPendingSessionEntryPoint,
|
||||||
setPreviewPlaybackState,
|
setPreviewPlaybackState,
|
||||||
setShowResumePrompt,
|
|
||||||
setWorkspaceMode,
|
setWorkspaceMode,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -383,7 +374,6 @@ export const useSpaceWorkspaceSessionControls = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setShowResumePrompt(false);
|
|
||||||
setPendingSessionEntryPoint('space-setup');
|
setPendingSessionEntryPoint('space-setup');
|
||||||
setPreviewPlaybackState('paused');
|
setPreviewPlaybackState('paused');
|
||||||
setWorkspaceMode('setup');
|
setWorkspaceMode('setup');
|
||||||
@@ -395,7 +385,6 @@ export const useSpaceWorkspaceSessionControls = ({
|
|||||||
pushStatusLine,
|
pushStatusLine,
|
||||||
setPendingSessionEntryPoint,
|
setPendingSessionEntryPoint,
|
||||||
setPreviewPlaybackState,
|
setPreviewPlaybackState,
|
||||||
setShowResumePrompt,
|
|
||||||
setWorkspaceMode,
|
setWorkspaceMode,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -477,7 +466,6 @@ export const useSpaceWorkspaceSessionControls = ({
|
|||||||
setGoalInput(updatedSession.goal);
|
setGoalInput(updatedSession.goal);
|
||||||
setLinkedFocusPlanItemId(updatedSession.focusPlanItemId ?? null);
|
setLinkedFocusPlanItemId(updatedSession.focusPlanItemId ?? null);
|
||||||
setSelectedGoalId(null);
|
setSelectedGoalId(null);
|
||||||
setShowResumePrompt(false);
|
|
||||||
return true;
|
return true;
|
||||||
}, [
|
}, [
|
||||||
currentSession,
|
currentSession,
|
||||||
@@ -485,7 +473,6 @@ export const useSpaceWorkspaceSessionControls = ({
|
|||||||
setGoalInput,
|
setGoalInput,
|
||||||
setLinkedFocusPlanItemId,
|
setLinkedFocusPlanItemId,
|
||||||
setSelectedGoalId,
|
setSelectedGoalId,
|
||||||
setShowResumePrompt,
|
|
||||||
updateCurrentIntent,
|
updateCurrentIntent,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ interface UseWorkspacePersistenceParams {
|
|||||||
selectedDurationMinutes: number;
|
selectedDurationMinutes: number;
|
||||||
selectedPresetId: string;
|
selectedPresetId: string;
|
||||||
goalInput: string;
|
goalInput: string;
|
||||||
showResumePrompt: boolean;
|
|
||||||
resumeGoal: string;
|
|
||||||
selectionOverride: SelectionOverride;
|
selectionOverride: SelectionOverride;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,8 +20,6 @@ export const useWorkspacePersistence = ({
|
|||||||
selectedDurationMinutes,
|
selectedDurationMinutes,
|
||||||
selectedPresetId,
|
selectedPresetId,
|
||||||
goalInput,
|
goalInput,
|
||||||
showResumePrompt,
|
|
||||||
resumeGoal,
|
|
||||||
selectionOverride,
|
selectionOverride,
|
||||||
}: UseWorkspacePersistenceParams) => {
|
}: UseWorkspacePersistenceParams) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -31,30 +27,21 @@ export const useWorkspacePersistence = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizedGoal = goalInput.trim().length > 0
|
|
||||||
? goalInput.trim()
|
|
||||||
: showResumePrompt
|
|
||||||
? resumeGoal
|
|
||||||
: '';
|
|
||||||
|
|
||||||
window.localStorage.setItem(
|
window.localStorage.setItem(
|
||||||
WORKSPACE_SELECTION_STORAGE_KEY,
|
WORKSPACE_SELECTION_STORAGE_KEY,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
sceneId: selectedScene.id,
|
sceneId: selectedScene.id,
|
||||||
durationMinutes: selectedDurationMinutes,
|
durationMinutes: selectedDurationMinutes,
|
||||||
soundPresetId: selectedPresetId,
|
soundPresetId: selectedPresetId,
|
||||||
goal: normalizedGoal,
|
|
||||||
override: selectionOverride,
|
override: selectionOverride,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
goalInput,
|
goalInput,
|
||||||
hasHydratedSelection,
|
hasHydratedSelection,
|
||||||
resumeGoal,
|
|
||||||
selectedPresetId,
|
selectedPresetId,
|
||||||
selectedScene.id,
|
selectedScene.id,
|
||||||
selectedDurationMinutes,
|
selectedDurationMinutes,
|
||||||
selectionOverride,
|
selectionOverride,
|
||||||
showResumePrompt,
|
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ export interface StoredWorkspaceSelection {
|
|||||||
sceneId?: string;
|
sceneId?: string;
|
||||||
durationMinutes?: number;
|
durationMinutes?: number;
|
||||||
soundPresetId?: string;
|
soundPresetId?: string;
|
||||||
goal?: string;
|
|
||||||
override?: Partial<SelectionOverride>;
|
override?: Partial<SelectionOverride>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,24 +6,20 @@ import {
|
|||||||
preloadAssetImage,
|
preloadAssetImage,
|
||||||
useMediaCatalog,
|
useMediaCatalog,
|
||||||
} from "@/entities/media";
|
} from "@/entities/media";
|
||||||
import { usePlanTier } from "@/entities/plan";
|
|
||||||
import { getSceneById, SCENE_THEMES } from "@/entities/scene";
|
import { getSceneById, SCENE_THEMES } from "@/entities/scene";
|
||||||
import { GOAL_CHIPS, SOUND_PRESETS, useThoughtInbox } from "@/entities/session";
|
import { useThoughtInbox } from "@/entities/session";
|
||||||
import {
|
import {
|
||||||
focusSessionApi,
|
focusSessionApi,
|
||||||
type CompletionResult,
|
type CompletionResult,
|
||||||
type CurrentSessionThought,
|
type CurrentSessionThought,
|
||||||
useFocusSessionEngine,
|
useFocusSessionEngine,
|
||||||
} from "@/features/focus-session";
|
} from "@/features/focus-session";
|
||||||
import { useFocusStats } from "@/features/stats";
|
|
||||||
import {
|
import {
|
||||||
useSoundPlayback,
|
useSoundPlayback,
|
||||||
useSoundPresetSelection,
|
useSoundPresetSelection,
|
||||||
} from "@/features/sound-preset";
|
} from "@/features/sound-preset";
|
||||||
import { useHudStatusLine } from "@/shared/lib/useHudStatusLine";
|
import { useHudStatusLine } from "@/shared/lib/useHudStatusLine";
|
||||||
import { copy } from "@/shared/i18n";
|
|
||||||
import { SpaceFocusHudWidget } from "@/widgets/space-focus-hud";
|
import { SpaceFocusHudWidget } from "@/widgets/space-focus-hud";
|
||||||
import { SpaceSetupDrawerWidget } from "@/widgets/space-setup-drawer";
|
|
||||||
import { CompletionResultModal } from "@/widgets/space-focus-hud/ui/CompletionResultModal";
|
import { CompletionResultModal } from "@/widgets/space-focus-hud/ui/CompletionResultModal";
|
||||||
import {
|
import {
|
||||||
findAtmosphereOptionForSelection,
|
findAtmosphereOptionForSelection,
|
||||||
@@ -35,7 +31,6 @@ import type { SessionEntryPoint, WorkspaceMode } from "../model/types";
|
|||||||
import { useSpaceWorkspaceSelection } from "../model/useSpaceWorkspaceSelection";
|
import { useSpaceWorkspaceSelection } from "../model/useSpaceWorkspaceSelection";
|
||||||
import { useSpaceWorkspaceSessionControls } from "../model/useSpaceWorkspaceSessionControls";
|
import { useSpaceWorkspaceSessionControls } from "../model/useSpaceWorkspaceSessionControls";
|
||||||
import {
|
import {
|
||||||
DURATION_SELECTION_OPTIONS,
|
|
||||||
resolveFocusTimeDisplayFromDurationMinutes,
|
resolveFocusTimeDisplayFromDurationMinutes,
|
||||||
resolveInitialDurationMinutes,
|
resolveInitialDurationMinutes,
|
||||||
} from "../model/workspaceSelection";
|
} from "../model/workspaceSelection";
|
||||||
@@ -55,8 +50,6 @@ export const SpaceWorkspaceWidget = () => {
|
|||||||
usedFallbackManifest,
|
usedFallbackManifest,
|
||||||
hasResolvedManifest,
|
hasResolvedManifest,
|
||||||
} = useMediaCatalog();
|
} = useMediaCatalog();
|
||||||
const { isPro } = usePlanTier();
|
|
||||||
const { review, summary: weeklySummary } = useFocusStats();
|
|
||||||
|
|
||||||
const initialSceneId = useMemo(() => SCENE_THEMES[0].id, []);
|
const initialSceneId = useMemo(() => SCENE_THEMES[0].id, []);
|
||||||
const initialScene = useMemo(
|
const initialScene = useMemo(
|
||||||
@@ -88,7 +81,6 @@ export const SpaceWorkspaceWidget = () => {
|
|||||||
>("paused");
|
>("paused");
|
||||||
const [pendingSessionEntryPoint, setPendingSessionEntryPoint] =
|
const [pendingSessionEntryPoint, setPendingSessionEntryPoint] =
|
||||||
useState<SessionEntryPoint>("space-setup");
|
useState<SessionEntryPoint>("space-setup");
|
||||||
const [showReviewTeaserAfterComplete, setShowReviewTeaserAfterComplete] = useState(false);
|
|
||||||
const [, setCurrentSessionThoughts] = useState<CurrentSessionThought[]>([]);
|
const [, setCurrentSessionThoughts] = useState<CurrentSessionThought[]>([]);
|
||||||
const [pendingCompletionResult, setPendingCompletionResult] = useState<CompletionResult | null>(null);
|
const [pendingCompletionResult, setPendingCompletionResult] = useState<CompletionResult | null>(null);
|
||||||
|
|
||||||
@@ -190,31 +182,8 @@ export const SpaceWorkspaceWidget = () => {
|
|||||||
setGoalInput: selection.setGoalInput,
|
setGoalInput: selection.setGoalInput,
|
||||||
setLinkedFocusPlanItemId: selection.setLinkedFocusPlanItemId,
|
setLinkedFocusPlanItemId: selection.setLinkedFocusPlanItemId,
|
||||||
setSelectedGoalId: selection.setSelectedGoalId,
|
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 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(() => {
|
useEffect(() => {
|
||||||
if (!isBootstrapping && !currentSession && !pendingCompletionResult) {
|
if (!isBootstrapping && !currentSession && !pendingCompletionResult) {
|
||||||
@@ -302,58 +271,6 @@ export const SpaceWorkspaceWidget = () => {
|
|||||||
<main className="relative flex-1" />
|
<main className="relative flex-1" />
|
||||||
</div>
|
</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 ? (
|
{isFocusMode ? (
|
||||||
<SpaceFocusHudWidget
|
<SpaceFocusHudWidget
|
||||||
sessionId={currentSession?.id ?? null}
|
sessionId={currentSession?.id ?? null}
|
||||||
@@ -429,7 +346,7 @@ export const SpaceWorkspaceWidget = () => {
|
|||||||
onClose={() => {
|
onClose={() => {
|
||||||
setPendingCompletionResult(null);
|
setPendingCompletionResult(null);
|
||||||
setCurrentSessionThoughts([]);
|
setCurrentSessionThoughts([]);
|
||||||
router.replace('/app');
|
void router.replace('/app');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user