// Vip Panel // Purpose: Defines the Vip Panel module and the local helpers/components used in this file. // Scope: Keeps behavior unchanged while isolating this concern into a clear, single-responsibility unit. import { useMemo, useState } from 'react'; import { useSessionActions, useSessionSelector } from '../../context/SessionContext.jsx'; import { useSettingsNamespace } from '../../settings/index.js'; import { COOKIE_KEY_REGEX, flowWrapClass } from '../vip/constants.js'; import VipAudioUploadCard from '../vip/VipAudioUploadCard/index.jsx'; import VipMidiBeeperCard from '../vip/VipMidiBeeperCard/index.jsx'; import VipVerificationCard from '../vip/VipVerificationCard.jsx'; import VipIdentityCard from '../vip/VipIdentityCard.jsx'; import VipPrivateRoverAccessCard from '../vip/VipPrivateRoverAccessCard.jsx'; import VipProfileImageCard from '../vip/VipProfileImageCard.jsx'; import VipRoverLearningCard from '../vip/VipRoverLearningCard.jsx'; export default function VipPanel() { const session = useSessionSelector((state) => state.session); const { identifySession, requestVerification, requestPrivateRoverAccess, playUploadedAudio, stopUploadedAudio, startMicWhip, readyMicWhip, stopMicWhip, } = useSessionActions(); const { value: identity, save: saveIdentity } = useSettingsNamespace('identity', { cookieUserId: '' }); const { value: profile } = useSettingsNamespace('profile', { nickname: '' }); const currentStoredKey = useMemo(() => (identity?.cookieUserId || '').trim(), [identity?.cookieUserId]); const nickname = (profile?.nickname || '').trim(); const isVerified = Boolean(session?.isVerified); const pendingRequestId = session?.verification?.pendingRequestId || null; const requestablePrivateRovers = session?.privateRoverAccess?.requestableRovers || []; const pendingPrivateRoverRequests = session?.privateRoverAccess?.pendingRequests || []; const ownRoverId = String(session?.assignment?.roverId || '').trim(); const [message, setMessage] = useState(''); const applyIdentityKey = async (nextRaw) => { const next = String(nextRaw || '').trim().toLowerCase(); if (!next) { throw new Error('Identity key required.'); } if (!COOKIE_KEY_REGEX.test(next)) { throw new Error('Identity key must match format: cu_ + 32 lowercase hex chars.'); } saveIdentity((current) => ({ ...(current || {}), cookieUserId: next })); await identifySession({ cookieUserId: next, nickname }); return next; }; // VipPanel owns its outer grid rather than placing it inside one CardFrame, // so it establishes its own query boundary for the same width-aware behavior. return (
{!isVerified ? (
) : null}
{isVerified ? (
) : (
Verify your account to unlock VIP features.
)}
{message ? (

{message}

) : null}
); }