learning_ai_invt_trdg/web/src/components/ProductAccessibilityGate.tsx

100 lines
2.7 KiB
TypeScript

import { useEffect, useState } from 'react';
import type { ReactNode } from 'react';
import { tradingKillSwitchClient } from '../lib/runtime';
interface AccessibilityState {
status: 'loading' | 'available' | 'maintenance' | 'product_disabled';
message?: string;
}
const initialState: AccessibilityState = {
status: 'loading',
};
export function ProductAccessibilityGate({ children }: { children: ReactNode }) {
const [state, setState] = useState<AccessibilityState>(initialState);
useEffect(() => {
let active = true;
async function loadAvailability() {
try {
const result = await tradingKillSwitchClient.check();
if (!active) {
return;
}
if (result.disabled) {
setState({
status: 'product_disabled',
message: result.message ?? 'Trading access is temporarily disabled.',
});
return;
}
setState({ status: 'available' });
} catch (error) {
// Fail open — kill switch service being down should not block users.
// The kill switch is a safety net, not an auth gate; degraded availability
// is preferable to a hard block when the check service is unreachable.
console.warn('[ProductAccessibilityGate] Kill switch check failed, defaulting to available.', error);
if (active) {
setState({ status: 'available' });
}
}
}
void loadAvailability();
return () => {
active = false;
};
}, []);
if (state.status === 'loading') {
return <CenteredMessage title="Loading trading workspace..." />;
}
if (state.status !== 'available') {
return (
<CenteredMessage
title={state.status === 'maintenance' ? 'Trading under maintenance' : 'Trading temporarily unavailable'}
body={state.message}
/>
);
}
return <>{children}</>;
}
function CenteredMessage({ title, body }: { title: string; body?: string }) {
return (
<div
style={{
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'var(--background)',
color: 'var(--foreground)',
padding: '2rem',
}}
>
<div
style={{
maxWidth: '32rem',
border: '1px solid var(--bl-border-subtle)',
borderRadius: '1rem',
padding: '2rem',
background: 'var(--bl-surface-overlay)',
boxShadow: 'var(--card-shadow)',
}}
>
<h1 style={{ margin: 0, fontSize: '1.5rem', marginBottom: '0.75rem' }}>{title}</h1>
{body ? (
<p style={{ margin: 0, color: 'var(--bl-text-quiet)', lineHeight: 1.6 }}>{body}</p>
) : null}
</div>
</div>
);
}