mirror of
https://github.com/legop3/MultiRoombaRover.git
synced 2026-09-16 01:21:20 -04:00
yayy!!!
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -11,8 +11,8 @@
|
|||||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||||
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
|
<meta name="apple-mobile-web-app-title" content="Multi Roomba Rover" />
|
||||||
<title>Multi Roomba Rover</title>
|
<title>Multi Roomba Rover</title>
|
||||||
<script type="module" crossorigin src="/assets/index-frHSA8Fb.js"></script>
|
<script type="module" crossorigin src="/assets/index-BY461TBg.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-Dx4QsRNa.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-DzG-Bp9_.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
+8
-2
@@ -85,12 +85,18 @@ function MobileFeatureTabs({
|
|||||||
}) {
|
}) {
|
||||||
const { session } = useSession();
|
const { session } = useSession();
|
||||||
const vipDotClass = session?.isVerified ? 'bg-emerald-400' : 'bg-amber-400';
|
const vipDotClass = session?.isVerified ? 'bg-emerald-400' : 'bg-amber-400';
|
||||||
|
const ownRoverId = String(session?.assignment?.roverId || '').trim();
|
||||||
|
const ownAudioForward = ownRoverId ? session?.audioForward?.[ownRoverId] : null;
|
||||||
|
const vipMicActive = Boolean(
|
||||||
|
ownAudioForward?.source === 'mic-whip' &&
|
||||||
|
(ownAudioForward?.state === 'starting' || ownAudioForward?.state === 'playing'),
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<section className="panel text-base">
|
<section className="panel text-base">
|
||||||
<Tabs defaultTab="chat">
|
<Tabs defaultTab="chat">
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab id="chat">Chat</Tab>
|
<Tab id="chat">Chat</Tab>
|
||||||
<Tab id="vip">
|
<Tab id="vip" highlight={vipMicActive ? 'pink' : 'none'}>
|
||||||
<span className="inline-flex items-center gap-0.5">
|
<span className="inline-flex items-center gap-0.5">
|
||||||
<span>VIP</span>
|
<span>VIP</span>
|
||||||
<span
|
<span
|
||||||
@@ -111,7 +117,7 @@ function MobileFeatureTabs({
|
|||||||
<RawUserPilePanel />
|
<RawUserPilePanel />
|
||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel id="vip">
|
<TabPanel id="vip" keepMounted>
|
||||||
<VipPanel />
|
<VipPanel />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel id="roomcontrols">
|
<TabPanel id="roomcontrols">
|
||||||
|
|||||||
@@ -112,12 +112,18 @@ function DriveDockPanel() {
|
|||||||
export default function RightPaneTabs({ layout, onOpenHelpOverlay }) {
|
export default function RightPaneTabs({ layout, onOpenHelpOverlay }) {
|
||||||
const { session } = useSession();
|
const { session } = useSession();
|
||||||
const vipDotClass = session?.isVerified ? 'bg-emerald-400' : 'bg-red-600';
|
const vipDotClass = session?.isVerified ? 'bg-emerald-400' : 'bg-red-600';
|
||||||
|
const ownRoverId = String(session?.assignment?.roverId || '').trim();
|
||||||
|
const ownAudioForward = ownRoverId ? session?.audioForward?.[ownRoverId] : null;
|
||||||
|
const vipMicActive = Boolean(
|
||||||
|
ownAudioForward?.source === 'mic-whip' &&
|
||||||
|
(ownAudioForward?.state === 'starting' || ownAudioForward?.state === 'playing'),
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<section className="panel text-base">
|
<section className="panel text-base">
|
||||||
<Tabs defaultTab="telemetry">
|
<Tabs defaultTab="telemetry">
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab id="telemetry">Controls</Tab>
|
<Tab id="telemetry">Controls</Tab>
|
||||||
<Tab id="vip">
|
<Tab id="vip" highlight={vipMicActive ? 'pink' : 'none'}>
|
||||||
<span className="inline-flex items-center gap-2">
|
<span className="inline-flex items-center gap-2">
|
||||||
<span>VIP</span>
|
<span>VIP</span>
|
||||||
<span
|
<span
|
||||||
@@ -153,7 +159,7 @@ export default function RightPaneTabs({ layout, onOpenHelpOverlay }) {
|
|||||||
<RoomCameraPanel defaultOrientation="horizontal" panelId="rightpane-telemetry" />
|
<RoomCameraPanel defaultOrientation="horizontal" panelId="rightpane-telemetry" />
|
||||||
</div>
|
</div>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel id="vip">
|
<TabPanel id="vip" keepMounted>
|
||||||
<VipPanel />
|
<VipPanel />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel id="help">
|
<TabPanel id="help">
|
||||||
|
|||||||
@@ -18,6 +18,11 @@ const TAB_VARIANTS = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const TAB_HIGHLIGHTS = {
|
||||||
|
none: '',
|
||||||
|
pink: 'bg-pink-500 text-white border-pink-200 hover:bg-pink-400',
|
||||||
|
};
|
||||||
|
|
||||||
const DEFAULT_VARIANT = 'primary';
|
const DEFAULT_VARIANT = 'primary';
|
||||||
|
|
||||||
function classNames(...parts) {
|
function classNames(...parts) {
|
||||||
@@ -89,17 +94,18 @@ export function TabList({ children, className = '' }) {
|
|||||||
return <div className={classNames('flex gap-0.5', className)}>{children}</div>;
|
return <div className={classNames('flex gap-0.5', className)}>{children}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Tab({ id, children, className = '', disabled = false }) {
|
export function Tab({ id, children, className = '', disabled = false, highlight = 'none' }) {
|
||||||
const { activeTab, setActiveTab, registerTab, variant } = useTabsContext();
|
const { activeTab, setActiveTab, registerTab, variant } = useTabsContext();
|
||||||
|
|
||||||
useEffect(() => registerTab(id), [id, registerTab]);
|
useEffect(() => registerTab(id), [id, registerTab]);
|
||||||
|
|
||||||
const variantStyles = TAB_VARIANTS[variant] ?? TAB_VARIANTS[DEFAULT_VARIANT];
|
const variantStyles = TAB_VARIANTS[variant] ?? TAB_VARIANTS[DEFAULT_VARIANT];
|
||||||
const isActive = activeTab === id;
|
const isActive = activeTab === id;
|
||||||
|
const highlightClass = TAB_HIGHLIGHTS[highlight] || TAB_HIGHLIGHTS.none;
|
||||||
|
|
||||||
const buttonClassName = classNames(
|
const buttonClassName = classNames(
|
||||||
variantStyles.base,
|
variantStyles.base,
|
||||||
isActive ? variantStyles.active : variantStyles.inactive,
|
highlightClass || (isActive ? variantStyles.active : variantStyles.inactive),
|
||||||
disabled ? 'cursor-not-allowed opacity-50' : '',
|
disabled ? 'cursor-not-allowed opacity-50' : '',
|
||||||
className
|
className
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -42,32 +42,66 @@ export default function VipPanel() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="panel-section space-y-0.5 text-base">
|
<section className="panel-section space-y-0.5 text-base">
|
||||||
{isVerified ? (
|
<div className="grid gap-0.5 grid-cols-1 lg:grid-cols-[minmax(0,1.25fr)_minmax(0,0.9fr)]">
|
||||||
<VipAudioUploadCard
|
<div className="lg:col-span-2">
|
||||||
ownRoverId={ownRoverId}
|
{isVerified ? (
|
||||||
audioForwardByRover={session?.audioForward || {}}
|
<section className="surface w-full">
|
||||||
playUploadedAudio={playUploadedAudio}
|
<div className="mx-auto flex w-full max-w-md flex-col items-center space-y-0.5 text-center">
|
||||||
stopUploadedAudio={stopUploadedAudio}
|
<p className="text-sm text-emerald-300">Verification complete</p>
|
||||||
startMicWhip={startMicWhip}
|
<p className="text-xs text-slate-400">VIP audio controls are unlocked for your assigned rover.</p>
|
||||||
readyMicWhip={readyMicWhip}
|
</div>
|
||||||
stopMicWhip={stopMicWhip}
|
</section>
|
||||||
/>
|
) : (
|
||||||
) : (
|
<VipVerificationCard
|
||||||
<VipVerificationCard
|
pendingRequestId={pendingRequestId}
|
||||||
pendingRequestId={pendingRequestId}
|
currentStoredKey={currentStoredKey}
|
||||||
currentStoredKey={currentStoredKey}
|
nickname={nickname}
|
||||||
nickname={nickname}
|
requestVerification={requestVerification}
|
||||||
requestVerification={requestVerification}
|
applyIdentityKey={applyIdentityKey}
|
||||||
applyIdentityKey={applyIdentityKey}
|
onMessage={setMessage}
|
||||||
onMessage={setMessage}
|
fullWidth
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<VipIdentityCard
|
{isVerified ? (
|
||||||
currentStoredKey={currentStoredKey}
|
<VipAudioUploadCard
|
||||||
applyIdentityKey={applyIdentityKey}
|
ownRoverId={ownRoverId}
|
||||||
onMessage={setMessage}
|
audioForwardByRover={session?.audioForward || {}}
|
||||||
/>
|
playUploadedAudio={playUploadedAudio}
|
||||||
|
stopUploadedAudio={stopUploadedAudio}
|
||||||
|
startMicWhip={startMicWhip}
|
||||||
|
readyMicWhip={readyMicWhip}
|
||||||
|
stopMicWhip={stopMicWhip}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<section className="surface h-full">
|
||||||
|
<div className="flex h-full flex-col items-center justify-center text-center text-xs text-slate-400">
|
||||||
|
Verify your account to unlock audio upload and microphone forwarding.
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<section className="surface h-full">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<p className="text-sm text-slate-200">VIP Notes</p>
|
||||||
|
<ul className="space-y-0.25 text-xs text-slate-400">
|
||||||
|
<li>Audio forwarding is locked to your currently assigned rover.</li>
|
||||||
|
<li>Losing control or turn ownership stops active forwarding.</li>
|
||||||
|
<li>PTT works page-wide using your keybind (default: m).</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="lg:col-span-2">
|
||||||
|
<VipIdentityCard
|
||||||
|
currentStoredKey={currentStoredKey}
|
||||||
|
applyIdentityKey={applyIdentityKey}
|
||||||
|
onMessage={setMessage}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{message ? (
|
{message ? (
|
||||||
<div className={flowWrapClass}>
|
<div className={flowWrapClass}>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { fieldClass, flowWrapClass, innerFlowClass } from './constants.js';
|
import { fieldClass } from './constants.js';
|
||||||
import { useControlSystem } from '../../controls/index.js';
|
import { useControlSystem } from '../../controls/index.js';
|
||||||
|
|
||||||
const MAX_UPLOAD_BYTES = 8 * 1024 * 1024;
|
const MAX_UPLOAD_BYTES = 8 * 1024 * 1024;
|
||||||
@@ -154,6 +154,18 @@ function waitForOutboundAudioFlow(pc, timeoutMs = 6000) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function StatusLight({ label, active, detail = '' }) {
|
||||||
|
return (
|
||||||
|
<div className="surface-muted flex items-center justify-between px-0.5 py-0.25 text-xs">
|
||||||
|
<span className="text-slate-300">{label}</span>
|
||||||
|
<span className="inline-flex items-center gap-0.5">
|
||||||
|
<span className={`h-2 w-2 rounded-full ${active ? 'bg-emerald-400' : 'bg-slate-600'}`} aria-hidden="true" />
|
||||||
|
<span className={active ? 'text-emerald-300' : 'text-slate-500'}>{detail || (active ? 'on' : 'off')}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function VipAudioUploadCard({
|
export default function VipAudioUploadCard({
|
||||||
ownRoverId = '',
|
ownRoverId = '',
|
||||||
audioForwardByRover = {},
|
audioForwardByRover = {},
|
||||||
@@ -182,6 +194,18 @@ export default function VipAudioUploadCard({
|
|||||||
[audioForwardByRover, roverId],
|
[audioForwardByRover, roverId],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const pipelineConnected = Boolean(
|
||||||
|
selectedForwardState && selectedForwardState.state !== 'offline' && !selectedForwardState.error,
|
||||||
|
);
|
||||||
|
const uploadPlaying = Boolean(
|
||||||
|
selectedForwardState?.source === 'upload' && selectedForwardState?.state === 'playing',
|
||||||
|
);
|
||||||
|
const micRelayActive = Boolean(
|
||||||
|
selectedForwardState?.source === 'mic-whip' &&
|
||||||
|
(selectedForwardState?.state === 'starting' || selectedForwardState?.state === 'playing'),
|
||||||
|
);
|
||||||
|
const micTalking = Boolean(audioTrackRef.current && (openMicEnabled || pttActive));
|
||||||
|
|
||||||
const handleUploadPlay = async () => {
|
const handleUploadPlay = async () => {
|
||||||
if (!roverId) {
|
if (!roverId) {
|
||||||
setMessage('Take control of your rover first.');
|
setMessage('Take control of your rover first.');
|
||||||
@@ -403,24 +427,27 @@ export default function VipAudioUploadCard({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={`surface ${flowWrapClass}`}>
|
<section className="surface h-full">
|
||||||
<div className={innerFlowClass}>
|
<div className="grid h-full gap-0.5 grid-rows-[auto_auto_auto_1fr_auto]">
|
||||||
<p className="text-sm text-slate-300">VIP Audio Upload</p>
|
<p className="text-sm text-slate-200 text-center">VIP Audio Forwarding</p>
|
||||||
<label className="grid w-full gap-0.5 text-xs text-slate-300">
|
|
||||||
|
<label className="mx-auto grid w-full max-w-sm gap-0.5 text-xs text-slate-300 text-center">
|
||||||
<span>Audio file (mp3 / wav / ogg)</span>
|
<span>Audio file (mp3 / wav / ogg)</span>
|
||||||
<input
|
<input
|
||||||
className={fieldClass}
|
className={`${fieldClass} text-center`}
|
||||||
type="file"
|
type="file"
|
||||||
accept=".mp3,.wav,.ogg,audio/mpeg,audio/wav,audio/ogg"
|
accept=".mp3,.wav,.ogg,audio/mpeg,audio/wav,audio/ogg"
|
||||||
disabled={working || !roverId}
|
disabled={working || !roverId}
|
||||||
onChange={(event) => setSelectedUpload(event.target.files?.[0] || null)}
|
onChange={(event) => setSelectedUpload(event.target.files?.[0] || null)}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{selectedUpload ? (
|
{selectedUpload ? (
|
||||||
<div className="surface-muted mx-auto w-full max-w-sm text-xs text-slate-300 text-center">
|
<div className="surface-muted mx-auto w-full max-w-sm text-xs text-slate-300 text-center">
|
||||||
{selectedUpload.name} ({selectedUpload.size} bytes)
|
{selectedUpload.name} ({selectedUpload.size} bytes)
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div className="flex justify-center gap-0.5">
|
<div className="flex justify-center gap-0.5">
|
||||||
<button type="button" className="button-dark text-sm" disabled={working || !roverId} onClick={handleUploadPlay}>
|
<button type="button" className="button-dark text-sm" disabled={working || !roverId} onClick={handleUploadPlay}>
|
||||||
{working ? 'Working...' : 'Play Upload'}
|
{working ? 'Working...' : 'Play Upload'}
|
||||||
@@ -430,28 +457,31 @@ export default function VipAudioUploadCard({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="surface-muted mx-auto flex w-full max-w-sm flex-col gap-0.5 p-0.5 text-xs text-slate-300 text-center">
|
<div className="grid gap-0.5 lg:grid-cols-2">
|
||||||
<p className="text-slate-200">Microphone Forwarding</p>
|
<div className="space-y-0.5">
|
||||||
<label className="flex items-center justify-center gap-0.5">
|
<p className="text-xs text-slate-300 text-center">Audio Path Status</p>
|
||||||
<input
|
<StatusLight label="Forward pipe" active={pipelineConnected} detail={pipelineConnected ? 'connected' : 'offline'} />
|
||||||
type="checkbox"
|
<StatusLight label="Upload playing" active={uploadPlaying} detail={uploadPlaying ? 'active' : 'idle'} />
|
||||||
checked={openMicEnabled}
|
<StatusLight label="Mic relay" active={micRelayActive} detail={micRelayActive ? 'active' : 'idle'} />
|
||||||
disabled={!roverId}
|
</div>
|
||||||
onChange={(event) => setOpenMicEnabled(Boolean(event.target.checked))}
|
|
||||||
/>
|
<div className="space-y-0.5">
|
||||||
<span>Open mic</span>
|
<p className="text-xs text-slate-300 text-center">Microphone</p>
|
||||||
</label>
|
<label className="surface-muted flex items-center justify-center gap-0.5 px-0.5 py-0.25 text-xs text-slate-300">
|
||||||
<p className="text-slate-400">PTT key: {controlState?.keymap?.micPtt?.[0] || 'm'} (hold)</p>
|
<input
|
||||||
<p className="text-slate-400">mic: {micState}</p>
|
type="checkbox"
|
||||||
<p className="text-slate-500">transport: whip</p>
|
checked={openMicEnabled}
|
||||||
|
disabled={!roverId}
|
||||||
|
onChange={(event) => setOpenMicEnabled(Boolean(event.target.checked))}
|
||||||
|
/>
|
||||||
|
<span>Open mic</span>
|
||||||
|
</label>
|
||||||
|
<StatusLight label="WHIP link" active={micState === 'live' || micState === 'starting'} detail={micState} />
|
||||||
|
<StatusLight label="PTT transmit" active={micTalking} detail={micTalking ? 'talking' : 'muted'} />
|
||||||
|
<div className="surface-muted text-center text-xs text-slate-400">PTT key: {controlState?.keymap?.micPtt?.[0] || 'm'} (hold)</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedForwardState ? (
|
|
||||||
<div className="surface-muted mx-auto w-full max-w-sm text-xs text-slate-300 text-center">
|
|
||||||
state: {selectedForwardState.state || 'idle'}
|
|
||||||
{selectedForwardState.error ? ` | error: ${selectedForwardState.error}` : ''}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{message ? <div className="text-xs text-slate-400 text-center">{message}</div> : null}
|
{message ? <div className="text-xs text-slate-400 text-center">{message}</div> : null}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { fieldClass, flowWrapClass, innerFlowClass, maskKey } from './constants.js';
|
import { fieldClass, flowWrapClass, innerFlowClass, maskKey } from './constants.js';
|
||||||
|
|
||||||
export default function VipIdentityCard({ currentStoredKey, applyIdentityKey, onMessage }) {
|
export default function VipIdentityCard({ currentStoredKey, applyIdentityKey, onMessage, fullWidth = false }) {
|
||||||
const [restoreFlowStep, setRestoreFlowStep] = useState(0);
|
const [restoreFlowStep, setRestoreFlowStep] = useState(0);
|
||||||
const [restoreKeyInput, setRestoreKeyInput] = useState('');
|
const [restoreKeyInput, setRestoreKeyInput] = useState('');
|
||||||
const [working, setWorking] = useState(false);
|
const [working, setWorking] = useState(false);
|
||||||
@@ -34,8 +34,10 @@ export default function VipIdentityCard({ currentStoredKey, applyIdentityKey, on
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const wrapClass = fullWidth ? 'w-full' : flowWrapClass;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={`surface ${flowWrapClass}`}>
|
<section className={`surface ${wrapClass}`}>
|
||||||
<div className={innerFlowClass}>
|
<div className={innerFlowClass}>
|
||||||
<p className="text-sm text-slate-300">Identity key</p>
|
<p className="text-sm text-slate-300">Identity key</p>
|
||||||
<p className="text-xs text-slate-500">Current: {maskKey(currentStoredKey) || 'not set yet'}</p>
|
<p className="text-xs text-slate-500">Current: {maskKey(currentStoredKey) || 'not set yet'}</p>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export default function VipVerificationCard({
|
|||||||
requestVerification,
|
requestVerification,
|
||||||
applyIdentityKey,
|
applyIdentityKey,
|
||||||
onMessage,
|
onMessage,
|
||||||
|
fullWidth = false,
|
||||||
}) {
|
}) {
|
||||||
const [requestFlowStep, setRequestFlowStep] = useState(0);
|
const [requestFlowStep, setRequestFlowStep] = useState(0);
|
||||||
const [requestKeyInput, setRequestKeyInput] = useState('');
|
const [requestKeyInput, setRequestKeyInput] = useState('');
|
||||||
@@ -53,9 +54,11 @@ export default function VipVerificationCard({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const wrapClass = fullWidth ? 'w-full' : flowWrapClass;
|
||||||
|
|
||||||
if (pendingRequestId) {
|
if (pendingRequestId) {
|
||||||
return (
|
return (
|
||||||
<section className={`surface text-sm text-slate-300 ${flowWrapClass}`}>
|
<section className={`surface text-sm text-slate-300 ${wrapClass}`}>
|
||||||
<div className={innerFlowClass}>Verification request pending: {pendingRequestId}</div>
|
<div className={innerFlowClass}>Verification request pending: {pendingRequestId}</div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
@@ -63,7 +66,7 @@ export default function VipVerificationCard({
|
|||||||
|
|
||||||
if (requestFlowStep === 0) {
|
if (requestFlowStep === 0) {
|
||||||
return (
|
return (
|
||||||
<section className={`surface ${flowWrapClass}`}>
|
<section className={`surface ${wrapClass}`}>
|
||||||
<div className={innerFlowClass}>
|
<div className={innerFlowClass}>
|
||||||
<p className="text-sm text-slate-300">Verification</p>
|
<p className="text-sm text-slate-300">Verification</p>
|
||||||
<button type="button" className="button-dark text-sm" onClick={beginRequestFlow} disabled={working}>
|
<button type="button" className="button-dark text-sm" onClick={beginRequestFlow} disabled={working}>
|
||||||
@@ -75,7 +78,7 @@ export default function VipVerificationCard({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className={`surface ${flowWrapClass}`} onSubmit={handleRequestSubmit}>
|
<form className={`surface ${wrapClass}`} onSubmit={handleRequestSubmit}>
|
||||||
<div className={innerFlowClass}>
|
<div className={innerFlowClass}>
|
||||||
<p className="text-sm text-slate-300">Request verification</p>
|
<p className="text-sm text-slate-300">Request verification</p>
|
||||||
<p className="text-xs text-slate-500">
|
<p className="text-xs text-slate-500">
|
||||||
|
|||||||
Reference in New Issue
Block a user