feat(space): timer 종료 모달과 10분 연장 추가

This commit is contained in:
2026-03-16 16:17:41 +09:00
parent 627bd82706
commit ec941f3cde
9 changed files with 271 additions and 21 deletions

View File

@@ -8,8 +8,11 @@ import { InlineMicrostep } from './InlineMicrostep';
import { ThoughtOrb } from './ThoughtOrb';
interface SpaceFocusHudWidgetProps {
sessionId?: string | null;
goal: string;
microStep?: string | null;
remainingSeconds?: number | null;
phaseStartedAt?: string | null;
timeDisplay?: string;
hasActiveSession?: boolean;
playbackState?: 'running' | 'paused';
@@ -17,14 +20,19 @@ interface SpaceFocusHudWidgetProps {
onIntentUpdate: (payload: { goal?: string; microStep?: string | null }) => boolean | Promise<boolean>;
onGoalUpdate: (nextGoal: string) => boolean | Promise<boolean>;
onGoalFinish: () => boolean | Promise<boolean>;
onTimerFinish: () => boolean | Promise<boolean>;
onAddTenMinutes: () => boolean | Promise<boolean>;
onStatusMessage: (payload: HudStatusLinePayload) => void;
onCaptureThought: (note: string) => void;
onExitRequested: () => void;
}
export const SpaceFocusHudWidget = ({
sessionId = null,
goal,
microStep,
remainingSeconds = null,
phaseStartedAt = null,
timeDisplay,
hasActiveSession = false,
playbackState = 'paused',
@@ -32,23 +40,34 @@ export const SpaceFocusHudWidget = ({
onIntentUpdate,
onGoalUpdate,
onGoalFinish,
onTimerFinish,
onAddTenMinutes,
onStatusMessage,
onCaptureThought,
onExitRequested,
}: SpaceFocusHudWidgetProps) => {
const [overlay, setOverlay] = useState<'none' | 'complete'>('none');
const [overlay, setOverlay] = useState<'none' | 'complete' | 'timer-complete'>('none');
const [completePreferredView, setCompletePreferredView] = useState<'choice' | 'next'>('choice');
const [isSavingIntent, setSavingIntent] = useState(false);
const visibleRef = useRef(false);
const timerPromptSignatureRef = useRef<string | null>(null);
const normalizedGoal = goal.trim().length > 0 ? goal.trim() : copy.space.focusHud.goalFallback;
const isCompleteOpen = overlay === 'complete';
const isCompleteOpen = overlay === 'complete' || overlay === 'timer-complete';
const timerCompletionSignature =
hasActiveSession &&
sessionPhase === 'focus' &&
remainingSeconds === 0 &&
phaseStartedAt
? `${sessionId ?? 'session'}:${phaseStartedAt}`
: null;
useEffect(() => {
if (!hasActiveSession) {
setOverlay('none');
setSavingIntent(false);
setCompletePreferredView('choice');
timerPromptSignatureRef.current = null;
}
}, [hasActiveSession]);
@@ -62,6 +81,19 @@ export const SpaceFocusHudWidget = ({
visibleRef.current = true;
}, [normalizedGoal, onStatusMessage, playbackState]);
useEffect(() => {
if (!timerCompletionSignature) {
return;
}
if (timerPromptSignatureRef.current === timerCompletionSignature) {
return;
}
timerPromptSignatureRef.current = timerCompletionSignature;
setOverlay('timer-complete');
}, [timerCompletionSignature]);
const handleOpenCompleteSheet = (preferredView: 'choice' | 'next' = 'choice') => {
setCompletePreferredView(preferredView);
setOverlay('complete');
@@ -139,15 +171,23 @@ export const SpaceFocusHudWidget = ({
<GoalCompleteSheet
open={isCompleteOpen}
currentGoal={goal}
mode={overlay === 'timer-complete' ? 'timer-complete' : 'manual'}
preferredView={completePreferredView}
onClose={() => setOverlay('none')}
onFinish={() => Promise.resolve(onGoalFinish())}
onFinish={() =>
overlay === 'timer-complete'
? Promise.resolve(onTimerFinish())
: Promise.resolve(onGoalFinish())
}
onExtendTenMinutes={() => Promise.resolve(onAddTenMinutes())}
onRest={() => {
setOverlay('none');
// The timer doesn't pause, they just rest within the flow.
}}
onConfirm={(nextGoal) => {
return Promise.resolve(onGoalUpdate(nextGoal));
return overlay === 'timer-complete'
? Promise.resolve(false)
: Promise.resolve(onGoalUpdate(nextGoal));
}}
/>
</div>