roverquesting

This commit is contained in:
legop3
2026-03-27 16:08:04 -04:00
parent 998933f4c2
commit c220eb4022
14 changed files with 508 additions and 131 deletions
+14
View File
@@ -5,12 +5,14 @@ import { COOKIE_KEY_REGEX, flowWrapClass } from './vip/constants.js';
import VipAudioUploadCard from './vip/VipAudioUploadCard.jsx';
import VipVerificationCard from './vip/VipVerificationCard.jsx';
import VipIdentityCard from './vip/VipIdentityCard.jsx';
import VipPrivateRoverAccessCard from './vip/VipPrivateRoverAccessCard.jsx';
export default function VipPanel() {
const {
session,
identifySession,
requestVerification,
requestPrivateRoverAccess,
playUploadedAudio,
stopUploadedAudio,
startMicWhip,
@@ -24,6 +26,8 @@ export default function VipPanel() {
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('');
@@ -84,6 +88,16 @@ export default function VipPanel() {
)}
</div>
<div className="lg:col-span-2">
<VipPrivateRoverAccessCard
requestableRovers={requestablePrivateRovers}
pendingRequests={pendingPrivateRoverRequests}
requestPrivateRoverAccess={requestPrivateRoverAccess}
onMessage={setMessage}
fullWidth
/>
</div>
<div className="lg:col-span-2">
<VipIdentityCard
currentStoredKey={currentStoredKey}
@@ -0,0 +1,82 @@
import { useMemo, useState } from 'react';
import { flowWrapClass, innerFlowClass } from './constants.js';
export default function VipPrivateRoverAccessCard({
requestableRovers = [],
pendingRequests = [],
requestPrivateRoverAccess,
onMessage,
fullWidth = false,
}) {
const [pendingByRover, setPendingByRover] = useState({});
const pendingMap = useMemo(() => {
const next = new Map();
(pendingRequests || []).forEach((entry) => {
if (!entry?.roverId) return;
next.set(String(entry.roverId), entry);
});
return next;
}, [pendingRequests]);
const wrapClass = fullWidth ? 'w-full' : flowWrapClass;
const handleRequest = async (roverId) => {
if (!roverId) return;
setPendingByRover((prev) => ({ ...prev, [roverId]: true }));
onMessage?.('');
try {
const response = await requestPrivateRoverAccess?.(roverId);
if (response?.existing) {
onMessage?.('You already have a pending request for that rover.');
} else {
onMessage?.('Private rover access request sent to lockdown admins.');
}
} catch (err) {
onMessage?.(err.message || 'Failed to send request.');
} finally {
setPendingByRover((prev) => ({ ...prev, [roverId]: false }));
}
};
return (
<section className={`surface text-sm text-slate-300 ${wrapClass}`}>
<div className={innerFlowClass}>
<p className="text-sm text-slate-300">Private rover access requests</p>
{requestableRovers.length === 0 ? (
<p className="text-xs text-slate-500">No closed private rovers are available to request right now.</p>
) : (
<div className="w-full space-y-0.5">
{requestableRovers.map((rover) => {
const roverId = String(rover.id);
const inFlight = Boolean(pendingByRover[roverId]);
const pending = pendingMap.get(roverId);
return (
<div key={roverId} className="surface-muted flex items-center justify-between gap-0.5 px-1 py-0.5">
<div className="min-w-0 text-left">
<p className="truncate text-xs font-semibold text-slate-100">{rover.name || roverId}</p>
<p className="truncate text-[0.7rem] text-slate-500">{roverId}</p>
</div>
{pending ? (
<span className="rounded bg-amber-700/30 px-1 py-0.5 text-[0.7rem] text-amber-200">
Pending
</span>
) : (
<button
type="button"
onClick={() => handleRequest(roverId)}
disabled={inFlight}
className="button-dark text-xs disabled:opacity-50"
>
{inFlight ? 'Sending...' : 'Request'}
</button>
)}
</div>
);
})}
</div>
)}
</div>
</section>
);
}
+3
View File
@@ -21,6 +21,7 @@ const SessionContext = createContext({
homeAssistantSetLightColor: async () => {},
setNickname: async () => {},
requestVerification: async () => {},
requestPrivateRoverAccess: async () => {},
triggerReplay: async () => {},
setCommunityGoal: async () => {},
setAdminReason: async () => {},
@@ -142,6 +143,8 @@ export function SessionProvider({ children }) {
emitWithAck('homeAssistant:lightColor', { entityId, rgbColor }),
setNickname: (nickname) => emitWithAck('nickname:set', { nickname }),
requestVerification: () => emitWithAck('verification:request'),
requestPrivateRoverAccess: (roverId) =>
emitWithAck('session:privateRover:requestAccess', { roverId }),
triggerReplay: (sources = []) => emitWithAck('replay:trigger', { sources }),
setCommunityGoal: (text) => emitWithAck('communityGoal:set', { text }),
setAdminReason: (text) => emitWithAck('adminReason:set', { text }),