import { useState } from 'react'; import { createPortal } from 'react-dom'; import { X, LogIn, ClipboardPaste } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { useAuthStore } from '../store/authStore'; import { parseOrbitShareLink, findSessionPlaylistId, readOrbitState, joinOrbitSession, } from '../utils/orbit'; import { showToast } from '../utils/toast'; interface Props { onClose: () => void; } /** * Orbit — manual join modal. Alternative to the Ctrl+V paste shortcut for * users who don't want to (or can't) paste the invite link into the app * directly. Reuses the same parse + preflight pipeline the clipboard * handler uses, so error surfaces stay consistent. */ export default function OrbitJoinModal({ onClose }: Props) { const { t } = useTranslation(); const [link, setLink] = useState(''); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const onPaste = async () => { try { const clip = await navigator.clipboard.readText(); if (clip) setLink(clip); } catch { /* silent — clipboard perms vary */ } }; const onJoin = async () => { setError(null); const text = link.trim(); if (!text) { setError(t('orbit.joinErrEmpty')); return; } const parsed = parseOrbitShareLink(text); if (!parsed) { setError(t('orbit.joinErrInvalid')); return; } const active = useAuthStore.getState().getActiveServer(); const activeUrl = (active?.url ?? '').replace(/\/+$/, ''); const wantUrl = parsed.serverBase.replace(/\/+$/, ''); if (activeUrl !== wantUrl) { setError(t('orbit.toastSwitchServer', { url: wantUrl })); return; } setBusy(true); try { const playlistId = await findSessionPlaylistId(parsed.sid); if (!playlistId) { setError(t('orbit.joinErrNotFound')); return; } const state = await readOrbitState(playlistId); if (!state) { setError(t('orbit.joinErrNotFound')); return; } if (state.ended) { setError(t('orbit.joinErrEnded')); return; } await joinOrbitSession(parsed.sid); showToast(t('orbit.toastJoined'), 2200, 'info'); onClose(); } catch (e) { setError(e instanceof Error ? e.message : t('orbit.toastJoinFail')); } finally { setBusy(false); } }; return createPortal(
{ if (e.target === e.currentTarget) onClose(); }} role="dialog" aria-modal="true" aria-labelledby="orbit-join-title" >

{t('orbit.joinModalTitle')}

{t('orbit.joinModalSub')}

{ setLink(e.target.value); setError(null); }} onKeyDown={e => { if (e.key === 'Enter' && !busy) void onJoin(); }} placeholder={t('orbit.joinModalLinkPlaceholder')} className="orbit-start-modal__input" />
{t('orbit.joinModalLinkHelper')}
{error &&
{error}
}
, document.body, ); }