import type { components } from '@pipu/api-client'; import { AGE_GROUPS, GENDERS, unwrap } from '@pipu/core'; import { useMutation } from '@tanstack/react-query'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useSession } from '../app/session'; import { Cover } from '../entities/catalog'; import { useErrorText } from '../shared/lib/errors'; import { checkIcon } from '../shared/ui/icons'; import { Button, cx, FormError, Icon } from '../shared/ui/ui'; import s from './onboarding.module.css'; type Options = components['schemas']['Onboarding']; /** Минимум исполнителей, чтобы волне было от чего оттолкнуться. */ const MIN_ARTISTS = 3; /** * Онбординг вкуса (docs/recommendations.md#7): исполнители и жанры библиотеки — начальный вкус «Моей волны», * пока своей истории почти нет. Пропуск тоже запоминается: второй раз не спросим. */ export function Onboarding({ options, onDone, editing = false }: { options: Options; onDone: () => void; editing?: boolean }) { const { t } = useTranslation(); const session = useSession(); const errorText = useErrorText(); const [artists, setArtists] = useState>(new Set(options.choice?.artist_ids)); const [genres, setGenres] = useState>(new Set(options.choice?.genres)); const [gender, setGender] = useState(options.choice?.gender ?? null); const [ageGroup, setAgeGroup] = useState(options.choice?.age_group ?? null); const save = useMutation({ mutationFn: async (skip: boolean) => unwrap( await session.client.PUT('/api/v1/me/onboarding', { body: skip ? { artist_ids: [], genres: [], gender: null, age_group: null } : { artist_ids: [...artists], genres: [...genres], gender, age_group: ageGroup }, }), ), onSuccess: onDone, }); // Функциональное обновление: быстрые нажатия подряд не теряют выбор. const toggle = (update: (fn: (prev: Set) => Set) => void, key: string) => update((prev) => { const next = new Set(prev); if (next.has(key)) next.delete(key); else next.add(key); return next; }); const enough = editing || artists.size >= MIN_ARTISTS || (artists.size > 0 && genres.size > 0) || !!gender || !!ageGroup || options.artists.length === 0; return (

{t('onboarding.title')}

{t('onboarding.subtitle')}

{t('onboarding.gender')}

{GENDERS.map((g) => )}

{t('onboarding.age_group')}

{t('onboarding.age_hint')}

{AGE_GROUPS.map((g) => )}

{t('onboarding.artists')}

{options.artists.map((a) => { const on = artists.has(a.id); return ( ); })}
{options.genres.length > 0 && (

{t('onboarding.genres')}

{options.genres.map((g) => ( ))}
)} {save.error ? errorText(save.error) : null}
); }