import { unwrap } from '@pipu/core'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useSession, useUser } from '../app/session'; import { usePlayer, usePlayerState } from '../player/context'; import { useErrorText } from '../shared/lib/errors'; import { pauseIcon, playIcon, waveBedtimeIcon, waveBoltIcon, waveCarIcon, waveFitnessIcon, waveHappyIcon, waveHeadphonesIcon, waveRainyIcon, waveRunIcon, waveSpaIcon, waveSunIcon, waveWeekendIcon, } from '../shared/ui/icons'; import { cx, FormError, Icon } from '../shared/ui/ui'; import { WaveButton } from '../widgets/wave/WaveButton'; import s from './home.module.css'; import { Onboarding } from './Onboarding'; /** Иконки Material Symbols по именам из /wave/options. */ const ICONS: Record = { directions_run: waveRunIcon, fitness_center: waveFitnessIcon, wb_sunny: waveSunIcon, headphones: waveHeadphonesIcon, directions_car: waveCarIcon, weekend: waveWeekendIcon, bedtime: waveBedtimeIcon, bolt: waveBoltIcon, sentiment_satisfied: waveHappyIcon, spa: waveSpaIcon, rainy: waveRainyIcon, }; const WAVE_COLORS: [string, string, string] = ['#2F80FF', '#7C5CFF', '#35D3FF']; type Tr = (key: string) => string; /** Главная: для нового пользователя без истории — сначала онбординг вкуса, затем волна. */ export function HomePage() { const session = useSession(); const queryClient = useQueryClient(); const onboarding = useQuery({ queryKey: ['onboarding'], queryFn: async () => unwrap(await session.client.GET('/api/v1/onboarding')), staleTime: Infinity, }); if (onboarding.data?.required) { return ( queryClient.setQueryData(['onboarding'], { ...onboarding.data, required: false })} /> ); } return ; } /** Главная (раскладка Center): кнопка «Моя волна» и под ней выбор занятия и настроения. */ function WaveHome() { const { t } = useTranslation(); const tr = t as unknown as Tr; const session = useSession(); const user = useUser(); const player = usePlayer(); const st = usePlayerState(); const errorText = useErrorText(); const [error, setError] = useState(null); const [activity, setActivity] = useState(st.wave?.activity ?? null); const [mood, setMood] = useState(st.wave?.mood ?? null); const options = useQuery({ queryKey: ['wave-options'], queryFn: async () => unwrap(await session.client.GET('/api/v1/wave/options')), staleTime: Infinity, }); const waveOn = st.wave !== null; const playing = waveOn && st.playing; const track = waveOn ? st.track : null; const colors: [string, string, string] = track?.album.cover_colors.length === 3 ? (track.album.cover_colors as [string, string, string]) : WAVE_COLORS; const run = (fn: () => Promise | void) => { setError(null); void Promise.resolve(fn()).catch(setError); }; const onWave = () => { if (!waveOn) run(() => player.startWave(activity, mood)); else run(() => player.toggle()); }; const choose = (kind: 'activity' | 'mood', key: string) => { const nextActivity = kind === 'activity' ? (activity === key ? null : key) : activity; const nextMood = kind === 'mood' ? (mood === key ? null : key) : mood; setActivity(nextActivity); setMood(nextMood); // Смена контекста во время игры — без остановки музыки. if (waveOn) run(() => player.setWaveContext(nextActivity, nextMood)); }; const ctxLabel = [activity && tr(`wave.activities.${activity}`), mood && tr(`wave.moods.${mood}`)].filter(Boolean).join(' · ') || t('wave.for_you'); return (
{t('wave.title')} {ctxLabel} {st.wave?.fallback &&

{t('wave.fallback')}

} {st.error === 'wave.empty' &&

{t('wave.empty')}

} {error ? errorText(error) : null} {options.data && (
tr(`wave.activities.${k}`)} onPick={(k) => choose('activity', k)} /> tr(`wave.moods.${k}`)} onPick={(k) => choose('mood', k)} />
)}
); } /** MoodPicker: одно занятие и одно настроение; повторный клик снимает выбор. */ function Chips({ title, options, value, label, onPick, }: { title: string; options: { key: string; icon: string }[]; value: string | null; label: (key: string) => string; onPick: (key: string) => void; }) { return (

{title}

{options.map((o) => ( ))}
); }