fec7f5d6d8
* Add first-run onboarding wizard for recording mechanics Introduce a minimal, skippable first-run wizard that teaches the record key. On first authenticated launch it renders as a full-screen takeover (gated above PusherProvider) so the stream/compose keyboard handlers are not mounted to compete for the same keys. Steps: welcome, hold ` to record, tap V to switch audio/video, tap ` to toggle recording, mic/camera permission priming, and a recap. The lessons detect the real gestures and never block — Esc or the Skip control dismisses at any point. Reuses existing pieces: VideoAudioToggle, useMediaDevices().requestLabels for permission priming, KeyHint, and isTypingTarget. Extracts the shared HOLD_THRESHOLD_MS into lib/constants and reuses it in the compose overlay. Resolves #259 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A9AHySdLb1zfZAEm6SuaQP * nits * light mode support for onboarding --------- Co-authored-by: Claude <noreply@anthropic.com>
61 lines
2.2 KiB
TypeScript
61 lines
2.2 KiB
TypeScript
import { useEffect } from 'react';
|
|
import { Check, Mic, Video } from 'lucide-react';
|
|
import { OnboardingStep, SUCCESS_DWELL_MS } from './onboarding-step';
|
|
import { useTapGesture } from './use-tap-gesture';
|
|
import { VideoAudioToggle } from '@/components/video-audio-toggle';
|
|
import { useMediaSettingsStore } from '@/stores/media-settings-store';
|
|
|
|
export function ToggleStep({ onAdvance }: { onAdvance: () => void }) {
|
|
const recordingMode = useMediaSettingsStore((s) => s.recordingMode);
|
|
const setRecordingMode = useMediaSettingsStore((s) => s.setRecordingMode);
|
|
|
|
const { satisfied } = useTapGesture({
|
|
targetKey: 'v',
|
|
onTap: () => {
|
|
const current = useMediaSettingsStore.getState().recordingMode;
|
|
setRecordingMode(current === 'video' ? 'audio' : 'video');
|
|
},
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (!satisfied) return;
|
|
const t = setTimeout(onAdvance, SUCCESS_DWELL_MS);
|
|
return () => clearTimeout(t);
|
|
}, [satisfied, onAdvance]);
|
|
|
|
// Teaching the toggle shouldn't permanently change the user's default mode.
|
|
useEffect(() => {
|
|
const original = useMediaSettingsStore.getState().recordingMode;
|
|
return () => setRecordingMode(original);
|
|
}, [setRecordingMode]);
|
|
|
|
const isVideo = recordingMode === 'video';
|
|
|
|
return (
|
|
<OnboardingStep
|
|
title={satisfied ? 'Audio or video, your call' : 'Switch audio and video'}
|
|
subtitle={
|
|
satisfied
|
|
? 'Tap V before recording to choose how you show up.'
|
|
: 'Tap the V key to switch between video and audio.'
|
|
}
|
|
status={
|
|
satisfied ? (
|
|
<span className="inline-flex items-center gap-1.5 text-emerald-600 dark:text-emerald-300">
|
|
<Check className="size-4" />{' '}
|
|
{isVideo ? 'Back to video.' : 'Audio it is.'}
|
|
</span>
|
|
) : null
|
|
}
|
|
>
|
|
<div className="flex size-32 flex-col items-center justify-center gap-2 rounded-2xl border border-border bg-muted text-foreground">
|
|
{isVideo ? <Video className="size-9" /> : <Mic className="size-9" />}
|
|
<span className="text-sm text-muted-foreground">
|
|
{isVideo ? 'Video' : 'Audio'}
|
|
</span>
|
|
</div>
|
|
<VideoAudioToggle />
|
|
</OnboardingStep>
|
|
);
|
|
}
|